IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java日志框架、springboot2日志、jcl说明、日志门面和实现 -> 正文阅读

[Java知识库]Java日志框架、springboot2日志、jcl说明、日志门面和实现

Java日志框架


文章目录


一、日志概述

1.1、日志文件

  • 日志文件是用于记录系统操作事件的文件集合。
  • 日志文件它具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要的作用。

1.2、调试日志

  • 我们平时在调试程序的过程中所使用的肯定就是专业开发工具自带的debug功能,可以实时查看程序运行情况,不能够有效保存运行情况的信息。
  • 调试日志是能够更加方便的去重现这些问题。

1.3、系统日志

系统日志包括系统日志、应用日志和安全日志这几种分类。

  • 系统日志是用来记录系统中硬件、软件和系统相关问题的信息。同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因,或者寻找收到攻击是留下的痕迹

二、日志框架

1.1、常见的日志框架

  • j.u.l

    j.u.l是java.util.logging包的简称,是JDK在1.4版本中引入的Java原生日志框架

  • Log4j

    Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式

  • Log4j2 Log4j官方的第二个版本,各个方面都是与Logback及其相似

    Log4j2已经不仅仅是Log4j的一个升级版本了,而是从头到尾被重写的,这可以认为这其实就是完全不同的两个框架

  • LogBack 由Log4j之父做的另一个开源项目

    LogBack也是一个很成熟的日志框架,其实LogBack和Log4j出自一个人之手,这个人就是Ceki Gülcü。
    logback当前分成三个模块:logback-core、logback- classic、logback-access。
    logback-core是其它两个模块的基础模块。
    logback-classic是Log4j的一个改良版本。
    此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如 Log4j或j.u.l。
    logback-access访问模块与Servlet容器集成提供通过Http来访问日记的功能。

1.2、日志框架的作用

  1. 控制日志输出的内容和格式。
  2. 控制日志输出的位置。
  3. 日志文件相关的优化,如异步操作、归档、压缩…
  4. 日志系统的维护
  5. 面向接口开发 – 日志的门面(jcl 、slf4j)

1.3、日志门面和日志框架的区别

1.3.1、种类和出现顺序

日志框架技术: JUL、Logback、Log4j、Log4j2

日志门面技术: JCL、SLF4j

出现顺序: log4j -->JUL–>JCL–> slf4j --> logback --> log4j2

1.3.2、看下阿里手册的说明

不知道有多少人看过《阿里巴巴Java开发手册》,其中有一条规范做了『强制』要求:
在这里插入图片描述

为什么阿里巴巴禁止工程师直接使用日志系统(Log4j、Logback)中的 API?
这边就是日志门面的就出来了

1.3.3、为什么要使用日志门面技术

日志门面技术 JCL、SLF4j

注明:
spring用JCL
springboot用SLF4j(后面会进行说明)

在软件开发领域有这样一句话:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。
而门面模式就是对于这句话的典型实践。

原因说明:

  • 每一种日志框架都有自己单独的API,要使用对应的框架就要使用对应的API,这就大大的增加了应用程序代码对于日志框架的耦合性
  • 我们使用了日志门面技术之后,对于应用程序来说,无论底层的日志框架如何改变,应用程序不需要修改任意一行代码,就可以直接上线了。

日志门面的一个比较重要的好处——解耦
可以成理解为项目面向统一抽象层编程。后面可以看slf4j进行理解

1.4、 日志级别

由低到高 trace < debug < info < warn < error


三、JUL(了解)

1.1、JUL简介

JUL全程 Java Util Logging,它是java原生的日志框架,使用时不需要另外引用第三方的类库,相对其他的框架使用方便,学习简单,主要是使用在 小型应用(Java基础的应用) 中。

1.2、JUL组件

图示:
在这里插入图片描述
说明:

  • Logger:被称为记录器,应用程序通过获取Logger对象,抵用其API来发布日志信息。Logger通常被认为是访问日志系统的入口程序。
  • Handler:处理器,每个Logger都会关联一个或者是一组Handler,Logger会将日志交给关联的Handler去做处理,由Handler负责将日志做记录。Handler具体实现了日志的输出位置,比如可以输出到控制台或者是文件中等等。
  • Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被略过。
  • Formatter:格式化组件,它负责对日志中的数据和信息进行转换和格式化,所以它决定了我们输出日志最终的形式。
  • Level:日志的输出级别,每条日志消息都有一个关联的级别。我们根据输出级别的设置,用来展现最终所呈现的日志信息。根据不同的需求,去设置不同的级别。

1.3、使用

package sqy.log;

/**
 * @author suqinyi
 * @Date 2021/9/21
 */
import org.junit.Test;
import java.util.logging.*;

public class JULTest {

	 /**
      * 其他自行了解。用得少。基本就是Java入门时候用用
      */
    @Test
    public void test01(){
        /**
         *  包:java.util.logging.Logger
         *     对于日志的输出,有两种方式
         *        第一种方式:
         *          直接调用日志级别相关的方法,方法中传递日志输出信息
         *        第二种方式:
         *          调用通用的log方法,然后在里面通过Level类型来定义日志的级别参数,以及搭配日志输出信息的参数
         */
        /**
         * 日志的级别( info,系统默认的日志级别)
         *     SEVERE : 错误 --- 最高级的日志级别
         *     WARNING : 警告
         *     INFO : (默认级别)消息
         *     CONFIG : 配置
         *     FINE : 详细信息(少)
         *     FINER : 详细信息(中)
         *     FINEST : 详细信息 (多) --- 最低级的日志级别
         */
        System.out.println(JULTest.class.getName());
        Logger logger = Logger.getLogger(JULTest.class.getName());
        logger.info("输出info信息1");//第一种方式
        logger.log(Level.INFO,"输出info信息2");//第二种方式 ( Level.INFO=>指定级别INFO )

        //占位符的方式输出
        String name = "zhangsan";
        int age = 18;
        logger.log(Level.INFO,"学生的姓名:{0},年龄:{1}",new Object[]{name,age});
    }
}

在这里插入图片描述


四、Log4j ( log for java)

Log4j官网文档

我们使用log4j技术,主要使用的是其配置文件

1.1、简介

  1. Log4j是Apache的一个开源项目
  2. 通过使用Log4j,可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
  3. 也可以控制每一条日志的输出格式
  4. 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程
  5. 通过配置文件来灵活地进行配置

1.2、组件

  • Loggers (日志记录器)
    Loggers 控制日志的输出以及输出级别
  • Appenders(输出控制器)
    指定日志的输出方式(输出到控制台、文件等)
  • Layout(日志格式化器)
    控制日志信息的输出格式

1.3、详细说明

1.3.1、常用日志级别

DEBUG < INFO < WARN < ERROR

1.3.2、5个常用Appenders

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

1.3.3、常用3个Layouts

Layouts提供四种日志输出样式,
如根据HTML样式 自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式

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

1.3.4、日志输出格式(占位符)说明

%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字符,就从左边交远销出的字符截掉

1.4、使用(输出、持久化到文件、持久化到数据库)

maven项目

pom文件:


<!-- log4j-->
<dependency>
	 <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
 </dependency>

 <!-- mysql -->
 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>5.1.21</version>
 </dependency>

 <!-- 单元测试4 -->
 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
 </dependency>

配置文件 log4j.properties(resources目录下)

#   %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 输出代码中的行号
#   %% 输出一个 "%" 字符
#   [%p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
#   可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式
#   [%10p][]中必须有10个字符,由空格来进行补齐,信息右对齐

#配置根节点logger
log4j.rootLogger=trace,console

#配置自定义logger
log4j.logger.com.bjpowernode.log4j.test=info,file

#配置apache的logger
log4j.logger.org.apache=error

#配置appender输出方式 输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#配置输出到控制台的格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

#配置appender输出方式 输出到文件
log4j.appender.file=org.apache.log4j.FileAppender
#配置输出到文件中的格式
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
#第一个file是我们自己命名的appenderName,第二个file是用来指定文件位置的属性
log4j.appender.file.file=D://test//log4j.log
#配置输出字符编码
log4j.appender.file.encoding=UTF-8


#RollingFileAppender的配置,我们可以针对于实际含义起名
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.rollingFile.file=D://test//log4j.log
log4j.appender.rollingFile.encoding=UTF-8
#指定日志文件内容大小
log4j.appender.rollingFile.maxFileSize=1MB
#指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex=5


#DailyRollingFileAppender的配置,我们可以针对于实际含义起名
log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.dailyRollingFile.file=D://test//log4j.log
log4j.appender.dailyRollingFile.encoding=UTF-8
log4j.appender.dailyRollingFile.datePattern='.'yyyy-MM-dd HH-mm-ss

#配置appender输出方式 输出到数据库表
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=root
log4j.appender.logDB.Sql=INSERT INTO tbl_log(name,createTime,level,category,fileName,message) values('project_log','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%m')

测试案例:

package sqy.log;

import org.junit.Test;
import org.apache.log4j.*;


/**
 * @author suqinyi
 * @Date 2021/9/21
 */
public class Log4jTest {

    @Test
    public void test01(){

        /**
         * 注意加载初始化信息:BasicConfigurator.configure();
         *   日志级别说明:Log4j提供了8个级别的日志输出(默认debug)
         *       ALL 最低等级 用于打开所有级别的日志记录
         *       TRACE 程序推进下的追踪信息,这个追踪信息的日志级别非常低,一般情况下是不会使用的
         *       DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要是配合开发,在开发过程中打印一些重要的运行信息
         *       INFO 消息的粗粒度级别运行信息
         *       WARN 表示警告,程序在运行过程中会出现的有可能会发生的隐形的错误
         *               注意,有些信息不是错误,但是这个级别的输出目的就是为了给程序员以提示
         *       ERROR 系统的错误信息,发生的错误不影响系统的运行
         *               一般情况下,如果不想输出太多的日志,则使用该级别即可
         *       FATAL 表示严重错误,它是那种一旦发生系统就不可能继续运行的严重错误
         *               如果这种级别的错误出现了,表示程序可以停止运行了
         *       OFF 最高等级的级别,用户关闭所有的日志记录
         *
         *   其中debug是我们在没有进行设置的情况下,默认的日志输出级别
         */

        Logger logger = Logger.getLogger(Log4jTest.class);
        logger.fatal("我是fatal信息");
        logger.error("我是error信息");
        logger.warn("我是warn信息");
        logger.info("我是info信息");
        logger.debug("我是debug信息");
        logger.trace("我是trace信息");
    }


    /**
     * 日志拆分
     *   将日志输出到文件中
     */
    @Test
    public void test02(){

        /**
         * 日志太多了,不方便管理和维护怎么办
         *     FileAppender为我们提供了好用的子类来进一步的对于文件输出进行处理
         *     RollingFileAppender
         *     DailyRollingFileAppender
         *
         *       1. RollingFileAppender
         *          protected long maxFileSize = 10485760L; 表示拆分文件的大小
         *          protected int maxBackupIndex = 1; 表示拆分文件的数量
         *
         *          #指定日志文件内容大小
         *          log4j.appender.rollingFile.maxFileSize=1MB
         *          #指定日志文件的数量
         *          log4j.appender.rollingFile.maxBackupIndex=5
         *
         *          只要文件超过1MB,那么则生成另外一个文件,文件的数量最多是5个
         *          文件1 记录日志 1MB
         *          文件2 记录日志 1MB
         *          ...
         *          ...
         *          文件5         1MB
         *
         *          如果5个文件不够怎么办,作为日志管理来讲,也不可能让日志无休止的继续增长下去
         *          所以,覆盖文件的策略是,按照时间来进行覆盖,原则就是保留新的,覆盖旧的
         *
         *      2.DailyRollingFileAppender(常用)
         *          按照时间来进行文件的拆分
         *          查看源码属性:
         *          private String datePattern = "'.'yyyy-MM-dd"; 默认是按照天来进行拆分的
         *
         *  注意:
         *     我们在练习的时候,可以根据秒来制定拆分策略
         *     但是实际生产环境中,根据秒生成日志文件是绝对不可能的
         *     如果是大型的项目,可以根据天进行拆分
         *     或者如果是小型的项目,可以根据周,月进行拆分
         */

        Logger logger = Logger.getLogger(Log4jTest.class);

        for (int i = 0; i < 10000; i++) {
            logger.fatal("fatal信息");
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }

    }

    /**
     * 将日志持久化到数据库表中
     */
    @Test
    public void test03(){
        /**
         *  创建表结构:(字段的制定可以根据需求进行调整)
         *          CREATE TABLE tbl_log(
         *              id int(11) NOT NULL AUTO_INCREMENT,
         *              name varchar(255) DEFAULT NULL COMMENT '项目名称',
         *              createTime varchar(255) DEFAULT NULL COMMENT '创建时间',
         *              level varchar(255) DEFAULT NULL COMMENT '日志级别',
         *              category varchar(255) DEFAULT NULL COMMENT '所在类的全路径',
         *              fileName varchar(255) DEFAULT NULL COMMENT '文件名称',
         *              message varchar(255) DEFAULT NULL COMMENT '日志消息',
         *              PRIMARY KEY(id)
         *          )
         */
        /*
            对于数据库表的日志输出进行相应配置
            #配置appender输出方式 输出到数据库表
            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 tbl_log(name,createTime,level,category,fileName,message) values('project_log','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%m')
         */

        Logger logger = Logger.getLogger(Log4jTest.class);
        logger.fatal("fatal信息");
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }
}


五、Logback

1.1、Logback说明

  • Logback是由log4j创始人设计的又一个开源日志组件。
  • Logback当前分成三个模块:logback-core,logback- classic和logback-access。
  • logback-core是其它两个模块的基础模块。
  • logback-classic完整实现SLF4J API。可以很方便地更换成其它日志系统如log4j或JDK14 Logging
  • logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。
  • logback-classic是log4j的一个改良版本
  • Logback性能比log4j强很多

1.2、Logback配置文件

  • logback.groovy
  • logback-test.xml
  • logback.xml
  • logback-spring.xml
  • 如果都不存在则采用默认的配置

1.2.1、配置文件分析解读

  1. 通用配置抽取EL表示式获取value值
  2. 控制台appender的配置
  3. 日志持久化到文件中的appender(普通文件、不拆分)
  4. 日志保存到html中的appender (html文件)
  5. 对日志文件进行 拆分归档 的appender配置(升级版)
  6. 控制台appender的过滤器的配置(过滤器)
  7. 异步日志的配置
  8. 引入自定义appender、配置隔离级别

详细信息看注释

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--configuration 根节点-->
<configuration>

    <!--
        配置文件通用属性
        <property name="" value=""></property>
        通过以${name}的形式,方便的取得value值  (el表达式的方式)
    -->
    <!--定义俩种格式化的方式 通过name来取值-->
    <property name="pattern01" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L %thread %m%n"></property>
    <property name="pattern02" value="[%-5level]%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m%n"></property>

    <!-- 日志的保存位置-->
    <property name="logDir" value="D://logs"></property>


    <!--================================通用配置===========================================-->

    <!-- 01、控制台appender 名字随便取不重复就行-->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!--
            System.out 表示以黑色字体输出日志(默认)
            System.err 表示以红色字体输出日志
        -->
        <target>System.err</target>
        <!-- 配置日志输出格式,直接引入上述的通用属性即可-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 格式引用通用属性配置  pattern01上面定义了-->
            <pattern>${pattern01}</pattern>
        </encoder>
    </appender>

    <!-- 02、日志保存到文件中的appender (普通文件)-->
    <appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
        <!-- 引入文件位置和文件名 -->
        <file>${logDir}/logback.log</file>
        <!-- 设置输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern01}</pattern>
        </encoder>
    </appender>


    <!-- 03、日志保存到html中的appender (html文件)-->
    <appender name="htmlAppender" class="ch.qos.logback.core.FileAppender">
        <!--文件位置和文件名称-->
        <file>${logDir}/logback.html</file>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <!--HTMLLayout-->
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>${pattern02}</pattern>
            </layout>
        </encoder>
    </appender>

    <!-- 04、对日志文件进行 拆分归档 的appender配置 -->
    <appender name="rollAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 输入格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern01}</pattern>
        </encoder>
        <!-- 文件位置和名称 -->
        <file>${logDir}/roll_logback.log</file>
        <!-- 指定拆分规则 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 按照时间和压缩格式声明文件名 压缩格式zip -->
            <fileNamePattern>${logDir}/roll.%d{yyyy-MM-dd}.log%i.zip</fileNamePattern>
            <!-- 按照文件大小来进行拆分 -->
            <maxFileSize>1KB</maxFileSize>
        </rollingPolicy>
    </appender>


    <!--05、 控制台appender的过滤器 (过滤器)-->
    <appender name="filterConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!--红色字体输出。默认黑色-->
        <target>System.err</target>
        <!--指定格式化的格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern01}</pattern>
        </encoder>
        <!-- 配置过滤器 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--设置日志的输出级别 -->
            <level>ERROR</level>
            <!--表示:高于level中设置的级别,则打印日志 -->
            <onMatch>ACCEPT</onMatch>
            <!--表示: 低于level中设置的级别,则屏蔽日志 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--06、配置异步日志 -->
    <appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
        <!--引入控制台的consoleAppender (引入的appender看自己需求)-->
        <appender-ref ref="consoleAppender"/>
    </appender>

    <!--07、配置隔离级别 all表示全部 -->
    <root level="ALL">
        <!--引入我们定义的appender-->
        <appender-ref ref="consoleAppender"/><!--控制台-->
        <appender-ref ref="filterConsoleAppender"/><!--控制台输出的过滤器  (注释掉consoleAppender)-->

        <appender-ref ref="fileAppender"/><!--普通文件持久化 (不拆分会导致一个文件过大)-->
        <appender-ref ref="htmlAppender"/><!--保存到html文件中,可以改html的样式,数据量不大用这个。可读性强-->

        <appender-ref ref="rollAppender"/><!--对日志文件拆分压缩 就是fileAppender的升级版,直接用这个就行了。fileAppender可以注释掉-->

        <appender-ref ref="asyncAppender"/><!--异步日志 提高性能(原理是多线程抢占线程)(注释掉在异步引入的Appender例如consoleAppender)-->
    </root>


</configuration>
<!--
  %-10level  级别 10个字符,左对齐
  %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
  %c  当前类全限定名
  %M  当前执行日志的方法
  %L  行号
  %thread 线程名称
  %m或者%msg   信息
  %n  换行
 -->

1.3、案例使用

pom文件

<!-- slf4j日志门面 -->
<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.25</version>
 </dependency>
 
 <!-- logback日志实现 -->
 <dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.2.3</version>
 </dependency>
 
  <!-- junit单元测试 -->
 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
 </dependency>

测试代码

package sqy;

import com.bjpowernode.logback.test01.LOGBACKTest01;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/22
 */
public class LogBackTest {

    /**
     *  logback有5种级别的日志输出   trace < debug < info < warn < error
     *  默认的日志级别是debug
     *  没使用配置文件
     */
    @Test
    public void test01(){
        Logger logger = LoggerFactory.getLogger(LogBackTest.class);
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }


    /**
     * 自定义配置文件logback.xml
     * 测试1、    测试控制台过滤器(注释掉引入的consoleAppender)
     * 测试2、    日志文件的归档压缩(注释掉引入的fileAppender)
     * 测试3、    异步日志(注释掉引入的consoleAppender)
     */
    @Test
    public void test02(){
        for (int i = 0; i < 50; i++) {
            Logger logger = LoggerFactory.getLogger(LogBackTest.class);
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
        System.out.println("开启异步日志===>01");
        System.out.println("开启异步日志===>02");
        System.out.println("开启异步日志===>03");
        System.out.println("开启异步日志===>04");

    }

}

拆分压缩效果:
在这里插入图片描述
html和普通文件效果
在这里插入图片描述
异步日志效果
在这里插入图片描述

1.4、Logback异步日志说明( log4j2修复提升)

可配置属性

可配置属性 < discardingThreshold>0< /discardingThreshold>
设置为0,说明永远都不会丢弃trace、debug、info这3个级别的日志
问题在于:
配置了一个阈值当队列的剩余容量小于这个阈值的时候,当前日志的级别 trace、debug、info这3个级别的日志将被丢弃

配置队列的深度

配置队列的深度 < queueSize>256</ queueSize>,这个值会影响记录日志的性能
默认值就是256

  • 关于这两个属性,一般情况下,我们使用默认值即可
  • 不要乱配置,会影响系统性能,了解其功能即可

六、JCL(日志门面-spring5用这个)

1.1 说明

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

  • 用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的jul, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库
  • common-logging内部有一个Simple logger的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j以及其他日志框架来使用

好处:

使用它的好处就是,代码依赖是common-logging而非log4j的API,避免了和具体的日志API直接耦合,在有必要时,可以更改日志实现的第三方库

JCL 有两个基本的抽象类:

  • Log:日志记录器
  • LogFactory:日志工厂(负责创建Log实例)

1.2、JCL源码剖析

这看下这篇文章就可以==>文章地址

1.3、JCL原则

JCL使用原则:
如果有log4j,优先使用log4j
如果没有任何第三方日志框架的时候,我们使用的就是JUL

1.4 、案例

1.4.1 JCL默认会使用JUL的api案例

pom文件

<!--jcl依赖-->
 <dependency>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
     <version>1.2</version>
 </dependency>

 <!--单元测试-->
 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
 </dependency>

测试案例

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;

/**
 * @author suqinyi
 * @Date 2021/9/21
 */
public class JCLTest {

    /**
     * pom文件不引入log4j测试
     */
    @Test
    public void test01(){
        /**
         * JCL使用原则:
         *    如果有log4j,优先使用log4j
         *    如果没有任何第三方日志框架的时候,我们使用的就是JUL(jdk自带的)
         */
        Log log = LogFactory.getLog(JCLTest.class);
        log.info("info信息");

    }
}

1.4.2 jcl+log4j 和源码分析

pom文件

<!--jcl依赖-->
 <dependency>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
     <version>1.2</version>
 </dependency>

<!--
	导入log4j会让jcl使用log4j的api而不使用jul的api
-->
 <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>

resources创建log4j.properties

log4j.rootLogger=trace,console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern=[%-10p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%

测试代码和源码分析

package com.bjpowernode.jcl.test;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;

public class JCLTest {

    /**
     * pom引入log4j依赖
     * resources目录创建:log4j.properties
     *    变成 门面jcl +  log4j(实际调用api)
     */
    @Test
    public void test02(){
        /*
            日志门面技术的好处:
                门面技术是面向接口的开发,不再依赖具体的实现类,减少代码的耦合性
                可以根据实际需求,灵活的切换日志框架
                统一的API,方便开发者学习和使用
                统一的配置管理便于项目日志的维护工作

            源码剖析:
                Log接口的4个实现类
                JDk13
                JDK14 正常java.util.logging
                Log4j 我们集成的log4j
                Simple JCL自带实现类

                (1)查看Jdk14Logger证明里面使用的是JUL日志框架
                (2)查看Log4JLogger证明里面使用的是Log4j日志框架

                (3)观察LogFactory,看看如何加载的Logger对象
                     这是一个抽象类,无法实例化
                     需要观察其实现类LogFactoryImpl

                (4)观察LogFactoryImpl
                    真正加载日志实现使用的就是这个实现类LogFactoryImpl

                (5)进入getLog

                    进入getInstance

                    找到instance = this.newInstance(name);,继续进入

                    找到instance = this.discoverLogImplementation(name); 表示发现一个日志的实现

                    for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
                        result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
                    }
                    遍历我们拥有的日志实现框架
                    遍历的是一个数组,这个数组是按照
                    log4j
                    jdk14
                    jdk13
                    SimpleLogger
                    的顺序依次遍历
                    表示的是,第一个要遍历的就是log4j,如果有log4j则执行该日志框架
                    如果没有,则遍历出来第二个,使用jdk14的JUL日志框架
                    以此类推

                    result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
                    表示帮我们创建Logger对象
                    在这个方法中,我们看到了
                    c = Class.forName(logAdapterClassName, true, currentCL);
                    是取得该类型的反射类型对象

                    使用反射的形式帮我们创建logger对象
                    constructor = c.getConstructor(this.logConstructorSignature);
         */
        Log log = LogFactory.getLog(JCLTest.class);
        log.info("info信息");

    }

}

在这里插入图片描述


七、SLF4J (日志门面)

SLF4J官方文档

简单日志门面 (Simple Logging Facade For Java) SLF4J。

SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等

日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接

所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接

1.1、门面模式(外观模式)

GoF23种设计模式其中之一

  • 门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。
  • 外观模式主要是体现了Java中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单。

1.2、日志门面说明

  • 前面介绍的几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。
  • 为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

1.3、官网图示剖析说明

在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的

注明:每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
在这里插入图片描述

1.3.1、对官网图分成3种情况

第一种:没有日志实现(没人用)

在这里插入图片描述
第二种:不需要桥接器。无缝衔接

都是slf4j门面时间线后面提供的日志实现,所以API完全遵循slf4j进行的设计
那么我们只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接
nop虽然也划分到实现中了,但是他不实现日志记录

在这里插入图片描述

第三种:需要桥接器

都是slf4j门面时间线前面的日志实现,所以API不遵循slf4j进行设计
通过适配桥接的技术,完成的与日志门面的衔接

在这里插入图片描述

1.4、如何让系统中所有的日志都统一到slf4j =>官网图示

SLF4J官方说明
在这里插入图片描述
如何让系统中所有的日志都统一到slf4j

  1. 将系统中其他日志框架先排除出去
  2. 用中间包来替换原有的日志框架
  3. 我们导入slf4j其他的实现

1.5、 SLF4J集成案例

1.5.1、集成slf4j-simple和logback

集成俩个日志实现的测试 slf4j-simple 和 logback
通常是实现一个

结论: 如果有多个日志实现的话,默认使用先导入的实现

发现: 切换slf4j-simple和logback但是导入的Logger包是没变了。
底层实现随便变,用的还是门面的包。这就是面向统一抽象层

pom文件:

 <!--slf4j 核心依赖-->
<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.25</version>
</dependency>

 <!--slf4j 自带的简单日志实现 -->
 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-simple</artifactId>
     <version>1.7.25</version>
 </dependency>

 <!-- logback依赖 -->
 <dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.2.3</version>
 </dependency>

测试代码

package com.sqy;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/21
 */
public class SLF4JTest {

	/**
	  * slf4j-simple和logback一起集成测试
	  * 如果有多个日志实现的话,默认使用先导入的实现
	  */
    @Test
    public void test01() {
        /**
         *    
         *         日志是打印出来了
         *         通过这一句可以发现SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
         *         虽然集成了logback,但是我们现在使用的仍然是slf4j-simple
         *         
         *         事实上只要出现了这个提示
         *         SLF4J: Class path contains multiple SLF4J bindings.
         *         在slf4j环境下,证明同时出现了多个日志实现
         *         
         *         如果先导入logback依赖,后导入slf4j-simple依赖
         *         那么默认使用的就是logback依赖
         *         
         *         如果有多个日志实现的话,默认使用先导入的实现
         */
        Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
        logger.info("pom文件Simple在前logback在后");

    }
}

效果:
在这里插入图片描述
切换pom文件 logback在前simple在后=>效果:
在这里插入图片描述

1.5.2、只集成logback

pom文件:

 <!--slf4j 核心依赖-->
 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.25</version>
 </dependency>

 <!-- logback依赖 -->
 <dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.2.3</version>
 </dependency>

测试代码

package com.sqy;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/21
 */
public class SLF4JTest {

    /**
     * 实际应用只集成一个日志实现
     * 集成logback
     */
    @Test
    public void test02() {
        Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
        logger.info("pom文件Simple在前logback在后");
    }
}

效果:
在这里插入图片描述

1.5.3、集成nop测试

结果:nop是没有做成日志实现的

pom文件

 <!--slf4j 核心依赖-->
<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.25</version>
 </dependency>

 <!-- 导入nop -->
 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-nop</artifactId>
     <version>1.7.25</version>
 </dependency>

测试代码

package com.sqy;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/21
 */
public class SLF4JTest {

    /**
     * 集成nop
     */
    @Test
    public void test03() {
        Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
        logger.info("pom文件Simple在前logback在后");
    }
}

效果:
在这里插入图片描述

1.5.4、集成log4j(需要狸猫换太子一下)

pom文件:

<!--slf4j 核心依赖-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- 导入log4j适配器依赖-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

<!--导入log4j依赖-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

log4j.properties

# 自定义格式
log4j.rootLogger=trace,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern=[%-10p] %m%n

测试代码:

package com.sqy;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/21
 */
public class SLF4JTest {

    /**
     * 集成log4j
     */
    @Test
    public void test04() {
        Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
        logger.info("集成log4j");
    }
}

效果:
在这里插入图片描述

1.6、总结

不管底层的日志实现怎么切换。导入的logger还是sfl4j。这就是门面日志的效果

八、目前最强的Log4j2

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

  • 目前市面上最主流的日志门面就是SLF4J,虽然Log4j2 也是日志门面,因为它的日志实现功能非常强大,性能优越。所以我们一般情况下还是将 Log4j2 看作是日志的实现
  • SLF4j + Log4j2 的组合,是最强大的日志功能实现方式,是主流趋势。
  • 异步日志是log4j2最大的特色,其性能的提升主要也是从异步日志中受益。

1.1、Log4j2特征

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

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


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


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


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


1.2、异步日志

提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应的是Appender组件和Logger组件

1.2.1、AsyncAppender方式(用的少)

  • 是通过引用别的Appender来实现的,当有日志事件到达时,会开启另外一个线程来处理它们。
  • 需要注意的是,如果在Appender的时候出现异常,对应用来说是无法感知的。
  • AsyncAppender应该在它引用的Appender之后配置,默认使用 java.util.concurrent.ArrayBlockingQueue实现而不需要其它外部的类库。
  • 当使用此Appender的时候,在多线程的环境下需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影响。
  • 这时候,我们应该考虑使用无锁的异步记录器(AsyncLogger)。

1.2.2、AsyncLogger方式(主流)

  1. AsyncLogger才是log4j2实现异步最重要的功能体现,也是官方推荐的异步方式

  2. 它可以使得调用Logger.log返回的更快。你可以有两种选择:全局异步和混合异步

  3. 全局异步: 所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在jvm启动的时候增加一个参数即可实现。

  4. 混合异步: 你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。

    虽然Log4j2提供以一套异常处理机制,可以覆盖大部分的状态,但是还是会有一小部分的特殊情况是无法完全处理的,比如我们如果是记录审计日志(特殊情况之一),那么官方就推荐使用同步日志的方式,而对于其他的一些仅仅是记录一个程序日志的地方,使用异步日志将大幅提升性能,减少对应用本身的影响

  5. 混合异步的方式需要通过修改配置文件来实现,使用AsyncLogger标记配置

混合异步用得比较多。混合异步虽然没有全局异步异步方便但是胜在灵活,想哪一块同步就同步,哪一个异步就异步

1.2.3、性能比对

在这里插入图片描述

在这里插入图片描述

1.3、测试案例 异步日志(maven工程)

1.3.0、slf4j+log4j2整合需要的依赖

 <!-- slf4j日志门面 -->
<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.25</version>
 </dependency>
 
 <!-- log4j适配器 -->
 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-slf4j-impl</artifactId>
     <version>2.12.1</version>
 </dependency>

 <!-- log4j2日志门面 -->
 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-api</artifactId>
     <version>2.12.1</version>
 </dependency>

 <!-- log4j2日志实现 -->
 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-core</artifactId>
     <version>2.12.1</version>
 </dependency>

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

1.3.1、slf4j+log4j2整合流程

  1. 导入slf4j的日志门面
  2. 导入log4j2的适配器
  3. 导入log4j2的日志门面
  4. 导入log4j2的日志实现

执行原理:

slf4j门面调用的是log4j2的门面,再由log4j2的门面调用log4j2的实现

1.3.2、AsyncAppender测试

流程:

  1. 添加异步日志依赖
  2. 在Appenders标签中,对于异步进行配置
  3. 使用Async标签
  4. rootlogger引用Async

pom文件:引入依赖

 <!-- slf4j日志门面 -->
<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.25</version>
 </dependency>
 
 <!-- log4j适配器 -->
 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-slf4j-impl</artifactId>
     <version>2.12.1</version>
 </dependency>

 <!-- log4j2日志门面 -->
 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-api</artifactId>
     <version>2.12.1</version>
 </dependency>

 <!-- log4j2日志实现 -->
 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-core</artifactId>
     <version>2.12.1</version>
 </dependency>

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

 <!-- 单元测试 -->
 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
 </dependency>

创建 log4j2.xml 配置文件

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

    <!--01、配置全局通用属性-->
    <properties>
        <property name="logDir">D://log4j2_logs</property>
    </properties>

    <!-- 配置appender -->
    <Appenders>
        <!-- 第一种 配置控制台输出的Appender -->
        <Console name="consoleAppender" target="SYSTEM_OUT">
        </Console>

        <!--第二种 配置文件输出位置和文件名称-->
        <File name="fileAppender" fileName="${logDir}//log4j2.log">
            <!-- 配置文件输出格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
        </File>

        <!--
            第三种 按照指定规则来拆分日志文件
                    fileName:日志文件的名字
                    filePattern:日志文件拆分后文件的命名规则
                         $${date:yyyy-MM-dd}:根据日期当天,创建一个文件夹
                         例如:2021-01-01这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
                              2021-01-02这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
                    rollog-%d{yyyy-MM-dd-HH-mm}-%i.log
                         为文件命名的规则:%i表示序号,从0开始,目的是为了让每一份文件名字不会重复
        -->
        <RollingFile name="rollingFile" fileName="${logDir}/rollog.log"
                     filePattern="${logDir}/$${date:yyyy-MM-dd}/rollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!-- 日志消息格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
            <Policies>
                <!-- 在系统启动时,触发拆分规则,产生一个日志文件 -->
                <OnStartupTriggeringPolicy/>
                <!-- 按照文件的大小进行拆分 -->
                <SizeBasedTriggeringPolicy size="10KB"/>
                <!-- 按照时间节点进行拆分 拆分规则就是filePattern-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!-- 在同一目录下,文件的个数限制,如果超出了设置的数值,则根据时间进行覆盖,新的覆盖旧的规则-->
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>

        <!-- 03、配置异步日志 -->
        <Async name="myAsync">
            <!-- 将控制台输出做异步的操作 -->
            <AppenderRef ref="consoleAppender"/>
        </Async>
    </Appenders>

    <!-- 配置logger -->
    <Loggers>
        <!-- 配置rootlogger 配置日志级别 -->
        <Root level="trace">
            <!-- 引用Appender -->
            
            <!--<AppenderRef ref="consoleAppender"/>--><!--控制台输出-->
            <!--<AppenderRef ref="fileAppender"/>--><!--持久化日志,普通文件不拆分-->
            <!--<AppenderRef ref="rollingFile"/>--><!--持久化日志,拆分-->
            <AppenderRef ref="myAsync"/><!--引入异步的配置-->
        </Root>
    </Loggers>

</Configuration>

测试代码:

package com.sqy;

import com.bjpowernode01.Log4j2Test01;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/22
 */
public class Log4j2Test {

    /**
     * 测试AsyncAppender异步日志
     */
    @Test
    public void test01() {
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        //日志的记录
        for (int i = 0; i < 100; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }

        //系统业务逻辑
        for (int i = 0; i < 50; i++) {
            System.out.println("系统业务逻辑==>测试异步");
        }

    }
}

效果:
在这里插入图片描述

1.3.3、AsyncLogger全局异步测试

异步日志实现(单独分配线程做日志的记录)

  1. 所有的日志都是异步的日志记录,在配置文件上稍微x
  2. 只需要在类路径resources下添加一个properties属性文件,做一步配置即可
  3. 文件名要求是:log4j2.component.properties
  4. 内容: Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

log4j2.xml 配置文件:

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

    <!--01、配置全局通用属性-->
    <properties>
        <property name="logDir">D://log4j2_logs</property>
    </properties>

    <!-- 配置appender -->
    <Appenders>
        <!-- 第一种 配置控制台输出的Appender -->
        <Console name="consoleAppender" target="SYSTEM_OUT">
        </Console>

        <!--第二种 配置文件输出位置和文件名称-->
        <File name="fileAppender" fileName="${logDir}//log4j2.log">
            <!-- 配置文件输出格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
        </File>

        <!--
            第三种 按照指定规则来拆分日志文件
                    fileName:日志文件的名字
                    filePattern:日志文件拆分后文件的命名规则
                         $${date:yyyy-MM-dd}:根据日期当天,创建一个文件夹
                         例如:2021-01-01这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
                              2021-01-02这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
                    rollog-%d{yyyy-MM-dd-HH-mm}-%i.log
                         为文件命名的规则:%i表示序号,从0开始,目的是为了让每一份文件名字不会重复
        -->
        <RollingFile name="rollingFile" fileName="${logDir}/rollog.log"
                     filePattern="${logDir}/$${date:yyyy-MM-dd}/rollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!-- 日志消息格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
            <Policies>
                <!-- 在系统启动时,触发拆分规则,产生一个日志文件 -->
                <OnStartupTriggeringPolicy/>
                <!-- 按照文件的大小进行拆分 -->
                <SizeBasedTriggeringPolicy size="10KB"/>
                <!-- 按照时间节点进行拆分 拆分规则就是filePattern-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!-- 在同一目录下,文件的个数限制,如果超出了设置的数值,则根据时间进行覆盖,新的覆盖旧的规则-->
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
        
    </Appenders>

    <!-- 配置logger -->
    <Loggers>
        <!-- 配置rootlogger 配置日志级别 -->
        <Root level="trace">
            <!-- 引用Appender -->

            <AppenderRef ref="consoleAppender"/><!--控制台输出-->
            <!--<AppenderRef ref="fileAppender"/>--><!--持久化日志,普通文件不拆分-->
            <!--<AppenderRef ref="rollingFile"/>--><!--持久化日志,拆分-->
        </Root>
    </Loggers>

</Configuration>

log4j2.component.properties文件配置:

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

测试代码

package com.sqy;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/22
 */
public class Log4j2Test {

    /**
     *  异步日志实现 (单独分配线程做日志的记录)
     *  使用AsyncLogger的方式  全局异步
     *
     *      所有的日志都是异步的日志记录
     *      只需要在类路径resources下添加一个properties属性文件,做一步配置即可
     *      文件名要求是:log4j2.component.properties
     *               Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
     */
    @Test
    public void test02(){
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        //日志的记录
        for (int i = 0; i < 100; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }

        //系统业务逻辑
        for (int i = 0; i < 50; i++) {
            System.out.println("系统业务逻辑==>测试异步");
        }
    }
}

效果:
在这里插入图片描述

1.3.4、AsyncLogger混合异步测试

不需要log4j2.component.properties配置文件

log4j2.xml 配置文件:自定义logger指定在com.sqy包下日志为异步。其余同步

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

    <!--01、配置全局通用属性-->
    <properties>
        <property name="logDir">D://log4j2_logs</property>
    </properties>

    <!-- 配置appender -->
    <Appenders>
        <!-- 第一种 配置控制台输出的Appender -->
        <Console name="consoleAppender" target="SYSTEM_OUT">
        </Console>

        <!--第二种 配置文件输出位置和文件名称-->
        <File name="fileAppender" fileName="${logDir}//log4j2.log">
            <!-- 配置文件输出格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
        </File>

        <!--
            第三种 按照指定规则来拆分日志文件
                    fileName:日志文件的名字
                    filePattern:日志文件拆分后文件的命名规则
                         $${date:yyyy-MM-dd}:根据日期当天,创建一个文件夹
                         例如:2021-01-01这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
                              2021-01-02这个文件夹中,记录当天的所有日志信息(拆分出来的日志放在这个文件夹中)
                    rollog-%d{yyyy-MM-dd-HH-mm}-%i.log
                         为文件命名的规则:%i表示序号,从0开始,目的是为了让每一份文件名字不会重复
        -->
        <RollingFile name="rollingFile" fileName="${logDir}/rollog.log"
                     filePattern="${logDir}/$${date:yyyy-MM-dd}/rollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!-- 日志消息格式 -->
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/>
            <Policies>
                <!-- 在系统启动时,触发拆分规则,产生一个日志文件 -->
                <OnStartupTriggeringPolicy/>
                <!-- 按照文件的大小进行拆分 -->
                <SizeBasedTriggeringPolicy size="10KB"/>
                <!-- 按照时间节点进行拆分 拆分规则就是filePattern-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!-- 在同一目录下,文件的个数限制,如果超出了设置的数值,则根据时间进行覆盖,新的覆盖旧的规则-->
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>

    </Appenders>

    <!-- 配置logger -->
    <Loggers>
        <!--
            《混合异步》 重点 name="com.sqy"
            01、includeLocation="false"
                表示去除日志记录中的行号信息,这个行号信息非常的影响日志记录的效率(生产中都不加这个行号)
                严重的时候可能记录的比同步的日志效率还有低

            02、additivity="false" 表示不继承rootlogger
        -->
        <!-- 自定义logger,让自定义的logger为异步logger 只有在com.sqy类的才会异步。其他包的日志是同步-->
        <AsyncLogger name="com.sqy" level="trace" includeLocation="false" additivity="false">
            <!-- 将控制台输出consoleAppender,设置为异步打印 -->
            <AppenderRef ref="consoleAppender"/>
        </AsyncLogger>

        <!-- 配置rootlogger 配置日志级别 -->
        <Root level="trace">
            <!-- 引用Appender -->

            <AppenderRef ref="consoleAppender"/><!--控制台输出-->
            <!--<AppenderRef ref="fileAppender"/>--><!--持久化日志,普通文件不拆分-->
            <!--<AppenderRef ref="rollingFile"/>--><!--持久化日志,拆分-->
        </Root>
    </Loggers>

</Configuration>

测试代码01:包package com.sqy 效果异步日志

package com.sqy;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/22
 */
public class Log4j2Test {

    /**
     *  异步日志实现 (单独分配线程做日志的记录)
     *  使用AsyncLogger的方式
     *  混合异步 :可以在应用中同时使用同步日志和异步日志,这使得日志的配置及输出会更加的灵活
     *      不需要文件 log4j2.component.properties (删除)
     *
     *      <AsyncLogger name="com.sqy" level="trace" includeLocation="false" additivity="false">
     *         在com.sqy 包下的日志是异步日志
     *         在com.app 包下的日志是同步日志
     *
     *  注意:
     *     如果使用异步日志
     *         AsyncAppender、AsyncLogger不要同时出现,没有这个需求,效果也不会叠加
     *         如果同时出现,那么效率会以AsyncAppender为主  (AsyncAppender这个没AsyncLogger好)
     *
     *         AsyncLogger的全局异步和混合异步也不要同时出现,没有这个需求,效果也不会叠加
     */
    @Test
    public void test03(){
        Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
        //日志的记录
        for (int i = 0; i < 100; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }

        //系统业务逻辑
        for (int i = 0; i < 50; i++) {
            System.out.println("系统业务逻辑==>测试异步");
        }
    }
}

测试代码02:包package com.app 效果同步日志

package com.app;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author suqinyi
 * @Date 2021/9/22
 */
public class Log4j2Test_NoAsync {

    @Test
    public void test01(){
        Logger logger = LoggerFactory.getLogger(Log4j2Test_NoAsync.class);
        //日志的记录
        for (int i = 0; i < 100; i++) {
            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");
        }
        //系统业务逻辑
        for (int i = 0; i < 50; i++) {
            System.out.println("系统业务逻辑==>测试异步");
        }
    }
}

混合异步效果(异步):
在这里插入图片描述

com.app包下 Log4j2Test_NoAsync.java 测试效果(同步)
在这里插入图片描述

1.4、异步日志 注意事项(影响性能)

  1. AsyncAppender、AsyncLogger不要同时出现,效果也不会叠加
  2. 如果同时出现,那么效率会以AsyncAppender为主 (AsyncAppender这个没AsyncLogger好)
  3. AsyncLogger的全局异步和混合异步也不要同时出现,没有这个需求,效果也不会叠加

九、SpringBoot日志

最基本的依赖(看maven树)

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

SpringBoot使用它来做日志功能

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

1.1、SpringBoot默认使用slf4j+logbook

项目启动会先走spring-core的jcl但是后面会被boot给覆盖掉所以是springboot2还是使用slf4j+logbook

debug测试附上图
在这里插入图片描述
logback
在这里插入图片描述

加载的是slf4j
在这里插入图片描述

1.2、底层依赖关系

说明:
1、springboot2.0 starter-logging移除了jcl。转换器直接写到了jcl原本包里面
2、springboot2 的spring-core里面的jcl里面做了适配操作(详情看源码)这个配置


说明:项目启动会先走spring-core的jcl但是后面会被boot给覆盖掉所以是springboot2还是使用slf4j+logbook

在这里插入图片描述

在这里插入图片描述

1.2、springboot使用logback配置文件

因为springboot默认就是slf4j+logback
所以导入logback.xml是对日志更细粒度的掌控

指定配置

Logging SystemCustomization
Logbacklogback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy
Log4j2:log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging)logging.properties

logback推荐使用的配置文件是logback-spring.xml

  • logback.xml:直接就被日志框架识别了
  • logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能
<springProfile name="dev">
    <!-- configuration to be enabled when the "dev" profile is active -->
  	可以指定某段配置只在某个环境下生效  在dev环境生效
</springProfile>

进行引入logback-spring.xml就可以了

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 -->
    <property name="LOG_HOME" value="/app/log" />
    <!-- 定义日志文件名称 -->
    <property name="appName" value="atguigu-springboot"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志输出格式:
			%d表示日期时间,
			%thread表示线程名,
			%-5level:级别从左显示5个字符宽度
			%logger{50} 表示logger名字最长50个字符,否则按照句点分割。 
			%msg:日志消息,
			%n是换行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <springProfile name="dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
            </springProfile>
            <springProfile name="!dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
            </springProfile>
        </layout>
    </appender>

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->  
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!-- 
            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
            那些为了归档而创建的目录也会被删除。
            -->
            <MaxHistory>365</MaxHistory>
            <!-- 
            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式: -->     
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 
		logger主要用于存放日志对象,也可以定义日志类型、级别
		name:表示匹配的logger类型前缀,也就是包的前半部分
		level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
		additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
		false:表示只用当前logger的appender-ref,true:
		表示当前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!--  logger -->
    <logger name="com.sqy" level="debug" />
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>

    <!-- 
    root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
    要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration> 

1.3、springboot2切换为log4j2

1. 先排除依赖,导入log4j2

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
      <!-- 排除掉原始依赖 以此去除logback引用 -->
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-logging</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

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

2. 使用log4j2的配置文件(简化版本详细看log4j2那边)

这样就转换成功了。springboot日志就变成了slf4j(门面)+ log4j2(实现)

总结

这就是Java的日志框架拉。log4j2+sfl4j是主流

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-23 11:19:04  更:2021-09-23 11:21:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:29:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码