日志学习:日志门面SLF4J和日志实现log4j

1. 日志门面:JCL , slf4j

我们的系统变的更加复杂的时候,我们的日志就容易发生混乱。随着系统开发的进行,可能会更新不同的日志框架,造成当前系统中存在不同的日志依赖,让我们难以统一的管理和控制。就算我们强制要求所有的模块使用相同的日志框架,系统中也难以避免使用其他类似spring,mybatis等其他的第三方框架,它们依赖于我们规定不同的日志框架,而且他们自身的日志系统就有着不一致性,依然会出来日志体系的混乱。所以我们需要借鉴JDBC的思想,为日志系统也提供一套门面,那么我们就可以面向这些接口规范来开发,避免了直接依赖具体的日志框架。这样我们的系统在日志中,就存在了日志的门面和日志的实现。就是说使用了日志门面,更改日志框架的时候就不需要修改底层代码;日志门面与日志实现是接口与实现类的关系;

常见的日志门面 :
JCL、slf4j
常见的日志实现:
JUL、log4j、logback、log4j2

SLF4J的使用

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

package com.bjpowernode.slf4j.test01;

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

public class SLF4JTest01 {

    /*@Test
    public void test01(){

        *//*

            入门案例

                SLF4J对日志的级别划分
                trace、debug、info、warn、error五个级别
                trace:日志追踪信息
                debug:日志详细信息
                info:日志的关键信息 默认打印级别
                warn:日志警告信息
                error:日志错误信息

                在没有任何其他日志实现框架集成的基础之上
                slf4j使用的就是自带的框架slf4j-simple
                slf4j-simple也必须以单独依赖的形式导入进来

         *//*
        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
        logger.trace("trace信息");
        logger.debug("debug信息");
        logger.info("info信息");
        logger.warn("warn信息");
        logger.error("error信息");

    }

    @Test
    public void test02(){

        *//*

            我们输出动态的信息时
            也可以使用占位符的形式来代替字符串的拼接

            我们有些时候输出的日志信息,需要我们搭配动态的数据
            有可能是信息,有可能是数据库表中的数据
            总之我们这样做最大的好处就是能够让日志打印变得更加灵活
            如果是通过拼接字符串的形式,不仅麻烦,而且更重要的是可读性查
            我们的日志打印是支持以替代符的形式做日志信息拼接的
            一般情况下,几乎所有的日志实现产品,都会提供这种基础功能

         *//*

        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
        String name = "zs";
        int age = 23;
        //logger.info("学生信息-姓名:"+name+";年龄:"+age);
        //logger.info("学生信息-姓名:{},年龄:{}",new Object[]{name,age});
        logger.info("学生信息-姓名:{},年龄:{}",name,age);

    }

    @Test
    public void test03(){

        *//*

            日志对于异常信息的处理

                一般情况下,我们在开发中的异常信息,都是记录在控制台上(我们开发环境的一种日志打印方式)
                我们会根据异常信息提取出有用的线索,来调试bug

                但是在真实生产环境中(项目上线),对于服务器或者是系统相关的问题
                在控制台上其实也会提供相应的异常或者错误信息的输出
                但是这种错误输出方式(输出的时间,位置,格式...)都是服务器系统默认的

                我们可以通过日志技术,选择将异常以日志打印的方式,进行输出查看
                输出的时间,位置(控制台,文件),格式,完全由我们自己去进行定义

         *//*

        //System.out.println(123);

        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);

        try {
            Class.forName("aaa");
        } catch (ClassNotFoundException e) {
            //打印栈追踪信息
            //e.printStackTrace();
            logger.info("XXX类中的XXX方法出现了异常,请及时关注信息");
            //e是引用类型对象,不能根前面的{}做有效的字符串拼接
            //logger.info("具体错误是:{}",e);
            //我们不用加{},直接后面加上异常对象e即可
            logger.info("具体错误是:",e);
        }


    }
    @Test
    public void test04(){

        *//*

            集成其他日志实现之前
            观察官网图

            SLF4J日志门面,共有3种情况对日志实现进行绑定
            1.在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的
                值得大家注意的是,通过我们刚刚的演示,slf4j-simple是slf4j官方提供的
                使用的时候,也是需要导入依赖,自动绑定到slf4j门面上
                如果不导入,slf4j 核心依赖是不提供任何实现的
            2.logback和simple(包括nop)
                都是slf4j门面时间线后面提供的日志实现,所以API完全遵循slf4j进行的设计
                那么我们只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接
                值得一提的是nop虽然也划分到实现中了,但是他是指不实现日志记录(后续课程)
            3.log4j和JUL
                都是slf4j门面时间线前面的日志实现,所以API不遵循slf4j进行设计
                通过适配桥接的技术,完成的与日志门面的衔接

            试着将logback日志框架集成进来

            测试1:
                在原有slf4j-simple日志实现的基础上,又集成了logback
                通过测试,日志是打印出来了 java.lang.ClassNotFoundException: aaa
                通过这一句可以发现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依赖
                如果有多个日志实现的话,默认使用先导入的实现

            测试:
                将slf4j-simple注释掉
                只留下logback,那么slf4j门面使用的就是logback日志实现
                值得一提的是,这一次没有多余的提示信息
                所以在实际应用的时候,我们一般情况下,仅仅只是做一种日志实现的集成就可以了

            通过这个集成测试,我们会发现虽然底层的日志实现变了,但是源代码完全没有改变
            这就是日志门面给我们带来最大的好处
            在底层真实记录日志的时候,不需要应用去做任何的了解
            应用只需要去记slf4j的API就可以了
            值得一提的是,我们虽然底层使用的是log4j做的打印,但是从当前代码使用来看
            我们其实使用的仍然是slf4j日志门面,至于日志是log4j打印的(或者是logback打印的)
            都是由slf4j进行操作的,我们不用操心

         *//*

        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);

        try {
            Class.forName("aaa");
        } catch (ClassNotFoundException e) {
            logger.info("具体错误是:",e);
        }

    }

    @Test
    public void test05(){

        *//*

            使用slf4j-nop
                表示不记录日志
                在我们使用slf4j-nop的时候
                首先还是需要导入实现依赖
                这个实现依赖,根据我们之前所总结出来的日志日志实现种类的第二种
                与logback和simple是属于一类的
                通过集成依赖的顺序而定
                所以如果想要让nop发挥效果,禁止所有日志的打印
                那么就必须要将slf4j-nop的依赖放在所有日志实现依赖的上方


         *//*
        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);

        try {
            Class.forName("aaa");
        } catch (ClassNotFoundException e) {
            logger.info("具体错误是:",e);
        }

    }

    @Test
    public void test06(){

        *//*

            接下来我们来绑定log4j
            由于log4j是在slf4j之前出品的日志框架实现
            所以并没有遵循slf4j的API规范
            (之前集成的logback,是slf4j之后出品的日志框架实现
            logback就是按照slf4j的标准指定的API,所以我们导入依赖就能用)

            如果想要使用,需要绑定一个适配器
            叫做slf4j-log4j12
            再导入log4j的实现

            测试:
            log4j:WARN No appenders could be found for logger (com.bjpowernode.slf4j.test01.SLF4JTest01).
            log4j:WARN Please initialize the log4j system properly.
            log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

            虽然日志信息没有打印出来,那么根据警告信息可以得出:
            使用了log4j日志实现框架
            提示appender没有加载,需要在执行日志之前做相应的加载工作(初始化)
            我们可以将log4j的配置文件导入使用
            测试结果为log4j的日志打印,而且格式和级别完全是遵循log4j的配置文件进行的输出



         *//*
        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
        logger.trace("trace信息");
        logger.debug("debug信息");
        logger.info("info信息");
        logger.warn("warn信息");
        logger.error("error信息");


    }

    @Test
    public void test07(){

        *//*

            接下来我们来适配JDK14

                与上一个测试log4j导入适配器一样
                JUL也是slf4j之前出品的日志实现框架
                所以也需要相应的适配器

                适配器导入之后,JUL日志实现是不用导入依赖的
                因为JUL,是JDK内置的

                从测试结果来看,是JUL的日志打印,默认是info级别日志的输出

         *//*
        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
        logger.trace("trace信息");
        logger.debug("debug信息");
        logger.info("info信息");
        logger.warn("warn信息");
        logger.error("error信息");

    }

    @Test
    public void test08(){

        *//*

            绑定多个日志实现,会出现警告信息
            通过源码来看看其原理(看看slf4j的执行原理)

            进入到getLogger
            看到Logger logger = getLogger(clazz.getName());

            进入重载的getLogger
            ILoggerFactory iLoggerFactory = getILoggerFactory(); 用来取得Logger工厂实现的方法

            进入getILoggerFactory()
            看到以双重检查锁的方式去做判断
            执行performInitialization(); 工厂的初始化方法

            进入performInitialization()
            bind()就是用来绑定具体日志实现的方法

            进入bind()
            看到Set集合Set<URL> staticLoggerBinderPathSet = null;
            因为当前有可能会有N多个日志框架的实现
            看到staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();

            进入findPossibleStaticLoggerBinderPathSet()
            看到创建了一个有序不可重复的集合对象
            LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();
            声明了枚举类的路径,经过if else判断,以获取系统中都有哪些日志实现
            看到Enumeration paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }

            我们主要观察常量STATIC_LOGGER_BINDER_PATH
            通过常量我们会找到类StaticLoggerBinder
            这个类是以静态的方式绑定Logger实现的类
            来自slf4j-JDK14的适配器
            进入StaticLoggerBinder
            看到new JDK14LoggerFactory();
            进入JDK14LoggerFactory类的无参构造方法
            看到java.util.logging.Logger.getLogger("");
            使用的就是jul的Logger

            接着观察findPossibleStaticLoggerBinderPathSet
            看到以下代码,表示如果还有其他的日志实现
            while(paths.hasMoreElements()) {
                URL path = (URL)paths.nextElement();
                将路径添加进入
                staticLoggerBinderPathSet.add(path);
            }

            回到bind方法
            表示对于绑定多实现的处理
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            如果出现多日志实现的情况
            则会打印
            Util.report("Class path contains multiple SLF4J bindings.");

            总结:
                在真实生产环境中,slf4j只绑定一个日志实现框架就可以了
                绑定多个,默认使用导入依赖的第一个,而且会产生没有必要的警告信息



         *//*
        Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
        logger.trace("trace信息");
        logger.debug("debug信息");
        logger.info("info信息");
        logger.warn("warn信息");
        logger.error("error信息");

    }*/

    @Test
    public void test09(){

        *//*

            需求:
                假设我们项目一直以来使用的是log4j日志框架
                但是随着技术和需求的更新换代
                log4j已然不能够满足我们系统的需求
                我们现在就需要将系统中的日志实现重构为 slf4j+logback的组合
                在不触碰java源代码的情况下,将这个问题给解决掉

                首先将所有关于其他日志实现和门面依赖全部去除
                仅仅只留下log4j的依赖
                测试的过程中,只能使用log4j相关的组件

                此时需要将日志替换为slf4j+logback
                我们既然不用log4j了,就将log4j去除
                将slf4j日志门面和logback的日志实现依赖加入进来
                这样做,没有了log4j环境的支持,编译报错

                这个时候就需要使用桥接器来做这个需求了
                桥接器解决的是项目中日志的重构问题,当前系统中存在之前的日志API,可以通过桥接转换到slf4j的实现

                桥接器的使用步骤:
                1.去除之前旧的日志框架依赖
                    <dependency>
                        <groupId>log4j</groupId>
                        <artifactId>log4j</artifactId>
                        <version>1.2.17</version>
                    </dependency>
                2.添加slf4j提供的桥接组件
                    log4j相关的桥接器
                    <dependency>
                        <groupId>org.slf4j</groupId>
                        <artifactId>log4j-over-slf4j</artifactId>
                        <version>1.7.25</version>
                    </dependency>
                  桥接器加入后,代码编译就不报错了
                  测试:
                    日志信息输出
                    输出格式为logback
                    证明了现在使用的确实是slf4j门面+logback实现

                    在重构之后,就会为我们造成这样一种假象
                    使用的明明是log4j包下的日志组件资源
                    但是真正日志的实现,却是使用slf4j门面+logback实现
                    这就是桥接器给我们带来的效果

                  注意:
                      在桥接器加入之后,适配器就没有必要加入了
                      桥接器和适配器不能同时导入依赖
                      桥接器如果配置在适配器的上方,则运行报错,不同同时出现
                      桥接器如果配置在适配器的下方,则不会执行桥接器,没有任何的意义


         *//*
        Logger logger = LogManager.getLogger(SLF4JTest01.class);

        logger.info("info信息");


    }


    @Test
    public void test10(){

       *//*

            在配置了桥接器之后,底层就是使用slf4j实现的日志
            分析其中原理

            通过getLogger
            进入Log4jLoggerFactory
            Logger newInstance = new Logger(name); 新建logger对象

            进入构造方法
            protected Logger(String name) {
                super(name);
            }

            点击进入父类的构造方法
            Category(String name) {
                this.name = name;
                this.slf4jLogger = LoggerFactory.getLogger(name);
                if (this.slf4jLogger instanceof LocationAwareLogger) {
                    this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
                }
            }

            在这个Category构造方法中,核心代码
                this.slf4jLogger = LoggerFactory.getLogger(name);

            LoggerFactory来自于org.slf4j

         *//*
        Logger logger = LogManager.getLogger(SLF4JTest01.class);


    }
}





2. 日志实现

: LUL(JAVA原生的日志框架使用时不需要引入第三方类库)、logback(springboot2.0后官方推荐使用)、log4j、log4j2

JUL日志实现

    @Test    public void testQuick() throws Exception {       
     // 1.创建日志记录器对象        
     Logger logger = Logger.getLogger("com.itheima.log.JULTest");        
     // 2.日志记录输出        
     logger.info("hello jul");
     logger.log(Level.INFO, "info msg");
     String name = "jack";        
     Integer age = 18;        
     logger.log(Level.INFO, "用户信息:{0},{1}", new Object[]{name, age});
         }}

LOG4J2日志实现

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

第一种<!-- 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>


第二种<!--使用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>


package com.bjpowernode.log4j2.test01;

//import org.apache.logging.log4j.LogManager;
//import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test01 {

    @Test
    public void test01(){

        /*

            入门案例
                单纯的使用Log4j2的 门面+实现
                Log4j2和log4j提供了相同的日志级别输出
                默认为error级别信息的打印

                ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration
                表示我们还没有建立自己的配置文件
                如果没有建立配置文件,则使用默认的配置


         */
        /*Logger logger = LogManager.getLogger(Log4j2Test01.class);

        logger.fatal("fatal信息");
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");*/


    }

    @Test
    public void test02(){

        /*

            使用配置文件
                log4j2是参考logback创作出来的,所以配置文件也是使用xml
                log4j2同样是默认加载类路径(resources)下的log4j2.xml文件中的配置

                根标签,所有日志相关信息,都是在根标签中进行配置
                <Configuration status="debug" monitorInterval="数值"></Configuration>
                在根标签中,可以加属性
                status="debug" 日志框架本身的日志输出级别
                monitorInterval="5" 自动加载配置文件的间隔时间,不低于5秒

         */
        /*Logger logger = LogManager.getLogger(Log4j2Test01.class);

        logger.fatal("fatal信息");
        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");*/
    }

    @Test
    public void test03(){

        /*

            虽然log4j2也是日志门面,但是现在市场的主流趋势仍然是slf4j
            所以我们仍然需要使用slf4j作为日志门面,搭配log4j2强大的日志实现功能,进行日志的相关操作

            接下来我们配置的就是当今市场上的最强大,最主流的日志使用搭配方式:
            slf4j+log4j2

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

            执行原理:
                slf4j门面调用的是log4j2的门面,再由log4j2的门面调用log4j2的实现

         */

        Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");

    }
    @Test
    public void test04(){

        /*

            将日志输出到文件中


         */
        Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }

    @Test
    public void test05(){

        /*

            日志的拆分


         */
        Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);


        for (int i = 0; i < 2000; i++) {

            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");

        }

    }

    @Test
    public void test06(){

        /*

            异步日志实现(单独分配线程做日志的记录)
                方式1:使用AsyncAppender的方式

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



         */
        Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);

        //日志的记录
        for (int i = 0; i < 2000; i++) {

            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");

        }

        //系统业务逻辑
        for (int i = 0; i < 1000; i++) {

            System.out.println("------------------");

        }


    }
    @Test
    public void test07(){

        /*

            异步日志实现(单独分配线程做日志的记录)
                方式2:使用AsyncLogger的方式
                全局异步
                    所有的日志都是异步的日志记录,在配置文件上不用做任何的改动
                    只需要在类路径resources下添加一个properties属性文件,做一步配置即可
                    文件名要求是:log4j2.component.properties
                    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector


         */
        Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);

        //日志的记录
        for (int i = 0; i < 2000; i++) {

            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");

        }

        //系统业务逻辑
        for (int i = 0; i < 1000; i++) {

            System.out.println("------------------");

        }
    }

    @Test
    public void test08(){

        /*

            异步日志实现(单独分配线程做日志的记录)
                方式2:使用AsyncLogger的方式
                混合异步:
                    可以在应用中同时使用同步日志和异步日志,这使得日志的配置及输出会更加的灵活

                需求:
                    假设我们现在有自定义的logger -- com.bjpowernode
                    让自定义的logger是异步的
                    让rootlogger是同步的

                注意:
                    在做测试前,一定要将全局的异步配置注释掉

                对于当前的logger,Log4j2Test01.class
                Log4j2Test01本身就是在我们自定义的logger路径下的

                注意:
                    如果使用异步日志
                        AsyncAppender、AsyncLogger不要同时出现,没有这个需求,效果也不会叠加
                        如果同时出现,那么效率会以AsyncAppender为主

                        AsyncLogger的全局异步和混合异步也不要同时出现,没有这个需求,效果也不会叠加


         */
        Logger logger = LoggerFactory.getLogger(Log4j2Test01.class);

        //日志的记录
        for (int i = 0; i < 2000; i++) {

            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");

        }

        //系统业务逻辑
        for (int i = 0; i < 1000; i++) {

            System.out.println("------------------");

        }

    }



}

log4j2默认加载classpath下的 log4j2.xml 文件中的配置。

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

    <!--

        配置全局通用属性

    -->
    <properties>
        <property name="logDir">D://test</property>
    </properties>

    <!-- 配置appender -->
    <Appenders>

        <!-- 配置控制台输出 -->
        <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>

        <!-- 配置异步日志 -->
        <!--<Async name="myAsync">

            &lt;!&ndash; 将控制台输出做异步的操作 &ndash;&gt;
            <AppenderRef ref="consoleAppender"/>

        </Async>-->

    </Appenders>


    <!-- 配置logger -->
    <Loggers>

        <!-- 自定义logger,让自定义的logger为异步logger -->
        <!--

            includeLocation="false"
            表示去除日志记录中的行号信息,这个行号信息非常的影响日志记录的效率(生产中都不加这个行号)
            严重的时候可能记录的比同步的日志效率还有低

            additivity="false"
            表示不继承rootlogger

        -->
        <AsyncLogger name="com.bjpowernode" 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"/>-->
            <!--<AppenderRef ref="myAsync"/>-->
            <AppenderRef ref="consoleAppender"/>
        </Root>

    </Loggers>

</Configuration>

Logback日志实现:

Logback主要分为三个模块:
logback-core:其它两个模块的基础模块
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j
APIlogback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

package com.bjpowernode.logback.test01;

import ch.qos.logback.core.ConsoleAppender;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;

public class LOGBACKTest01 {

    @Test
    public void test01(){

        /*

            入门案例
                logback有5种级别的日志输出
                分别是
                trace < debug < info < warn < error

                通过信息打印,默认的日志级别是debug,trace信息没有打印出来
<dependency>    
<groupId>org.slf4j</groupId>    
<artifactId>slf4j-api</artifactId>    
<version>1.7.25</version>
</dependency>

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


         */
        Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");

    }

    @Test
    public void test02(){

        /*

            Logback配置文件的使用
                在resources下面,创建一份配置文件,命名为logback.xml

                一切配置都是在根标签中进行操作的
                <configuration>
                </configuration>

         */
        Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");

    }

    @Test
    public void test03(){

        /*

            在实际的生产环境中,我们更希望将日志信息保留在文件中

            在文件中,默认是以追加日志的形式做记录

         */
        Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }

    @Test
    public void test04(){

        /*

            将日志输出成为一个html文件
                这个html文件是由logback来帮我们控制样式以及输出的格式
                内容由我们自己来指定

                初始测试:样式不是很好看,需要将换行,空格都去除掉

                在实际生产环境中,如果日志不是很多,建议使用html的方式去进行日志的记录

         */
        Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }

    @Test
    public void test05(){

        /*

            日志拆分和归档压缩

                重要标签来源:
                查看源码
                RollingFileAppender类中找到rollingPolicy属性
                SizeAndTimeBasedRollingPolicy类中找到maxFileSize属性
                这些属性在类中都是以set方法的形式进行的赋值
                我们在配置文件中配置的信息,其实找到的都是这些属性的set方法

                在TimeBasedRollingPolicy找到
                static final String FNP_NOT_SET =
                "The FileNamePattern option must be set before using TimeBasedRollingPolicy. ";

                只要我们要使用到日志的拆分
                FileNamePattern属性是必须要使用到了


         */

        for (int i = 0; i < 1000; i++) {

            Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");

        }


    }

    @Test
    public void test06(){

        /*

            我们可以在appender中添加过滤器
            以此对日志进行更细粒度的打印


         */
        Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");

    }

    @Test
    public void test07(){

        /*

            按照我们当前的代码执行顺序
            代码肯定是按照从上向下的顺序执行
            上面的代码完全执行完毕后,才会执行下面的代码

            由此得出会出现的问题:
                只要是在记录日志,那么系统本身的功能就处于一种停滞的状态
                当日志记录完毕后,才会执行其他的代码
                如果日志记录量非常庞大的话,那么我们对于系统本身业务代码的执行效率会非常低

                所以logback为我们提供了异步日志的功能

                配置方式:

                    1.配置异步日志
                        在异步日志中引入我们真正需要输出的appender
                        <appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
                            <appender-ref ref="consoleAppender"/>
                        </appender>

                    2.在rootlogger下引入异步日志
                        <appender-ref ref="asyncAppender"/>

                所谓异步日志的原理是:
                    系统会为日志操作单独的分配出来一根线程,原来用来执行当前方法的主线程会继续向下执行
                    线程1:系统业务代码执行
                    线程2:打印日志
                    两根线程争夺CPU的使用权

                在实际项目开发中,越大的项目对于日志的记录就越庞大
                为了保证项目的执行效率,异步日志是一个很好的选择


         */

        Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

        //日志打印操作
        for (int i = 0; i < 100; i++) {

            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");

        }

        //系统本身业务相关的其他操作
        System.out.println("1----------------------");
        System.out.println("2----------------------");
        System.out.println("3----------------------");
        System.out.println("4----------------------");
        System.out.println("5----------------------");


    }


    @Test
    public void test08(){

        /*

            自定义logger

         */
        Logger logger = LoggerFactory.getLogger(LOGBACKTest01.class);

        logger.error("error信息");
        logger.warn("warn信息");
        logger.info("info信息");
        logger.debug("debug信息");
        logger.trace("trace信息");
    }

    @Test
    public void test09(){

        /*

            关于logback补充:

                1.异步日志:
                    可配置属性
                    配置了一个阈值
                    当队列的剩余容量小于这个阈值的时候,当前日志的级别 trace、debug、info这3个级别的日志将被丢弃
                    设置为0,说明永远都不会丢弃trace、debug、info这3个级别的日志
                    <discardingThreshold>0</discardingThreshold>
                    配置队列的深度,这个值会影响记录日志的性能,默认值就是256
                    <queueSize>256</queueSize>
                    关于这两个属性,一般情况下,我们使用默认值即可
                    不要乱配置,会影响系统性能,了解其功能即可

                2.关于不同的日志实现,配置文件也是不同的
                    例如:
                        log4j经常使用的是properties属性文件
                        logback使用的是xml配置文件

                        如果我们遇到了一种情况,就是需要将以前的log4j,改造为使用logback
                        应该如何处理
                        我们可以使用工具

                        访问logback官网
                        找到log4j.properties转换器

                        只要是二者兼容的技术,才会被翻译
                        如果是log4j独立的技术,logback没有,或者是有这个技术但是并不兼容转义
                        那么这个工具则不会为我们进行翻译

                        如果是遇到简单的配置,我们可以使用工具


         */


    }


}

logback.xml 配置文件

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

    <!--

        配置文件通用属性
        <property name="" value=""></property>
        所谓配置文件中的通用属性是为了让接下来的配置更加方便引用
        通过以${name}的形式,方便的取得value值
        通过取得的value值可以做文件的其他配置而使用

    -->

    <!--

        我们在此可以先做日志输出格式相关的配置
        %-10level  级别 案例为设置10个字符,左对齐
        %d{yyyy-MM-dd HH:mm:ss.SSS} 日期
        %c  当前类全限定名
        %M  当前执行日志的方法
        %L  行号
        %thread 线程名称
        %m或者%msg    信息
        %n  换行

        以property的形式将日志输出格式配置成为文件的通用的属性
        那么下面我们配置的输出方式中,就可以重复的引用该配置(以下的配置,对于输出格式就不用配置多次了)

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

    <!-- 配置文件的输出路径 -->
    <property name="logDir" value="D://test"></property>

    <!-- 配置文件的appender 普通文件-->
    <appender name="fileAppender" class="ch.qos.logback.core.FileAppender">

        <!-- 引入文件位置 -->
        <file>${logDir}/logback.log</file>

        <!-- 设置输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>

    </appender>

    <!-- 配置控制台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">

            <!-- 格式引用通用属性配置 -->
            <pattern>${pattern}</pattern>

        </encoder>

    </appender>

    <!-- 配置文件的appender html文件 -->
    <appender name="htmlFileAppender" class="ch.qos.logback.core.FileAppender">

        <file>${logDir}/logback.html</file>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">

            <layout class="ch.qos.logback.classic.html.HTMLLayout">

                <pattern>${pattern1}</pattern>

            </layout>

        </encoder>

    </appender>

    <!-- 配置文件的appender 可拆分归档的文件 -->
    <appender name="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">

        <!-- 输入格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!-- 引入文件位置 -->
        <file>${logDir}/roll_logback.log</file>

        <!-- 指定拆分规则 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

            <!-- 按照时间和压缩格式声明文件名 压缩格式gz -->
            <fileNamePattern>${logDir}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>

            <!-- 按照文件大小来进行拆分 -->
            <maxFileSize>1KB</maxFileSize>

        </rollingPolicy>

    </appender>

    <!-- 配置控制台的appender 使用过滤器 -->
    <appender name="consoleFilterAppender" class="ch.qos.logback.core.ConsoleAppender">

        <target>
            System.err
        </target>

        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>

        <!-- 配置过滤器 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <!-- 设置日志的输出级别 -->
            <level>ERROR</level>

            <!-- 高于level中设置的级别,则打印日志 -->
            <onMatch>ACCEPT</onMatch>

            <!-- 低于level中设置的级别,则屏蔽日志 -->
            <onMismatch>DENY</onMismatch>

        </filter>

    </appender>

    <!-- 配置异步日志 -->
    <appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="consoleAppender"/>
    </appender>

    <!--

        日志记录器
        配置root logger
        level:配置日志级别

        可以同时配置多个appender,做日志的多方向输出

    -->
    <root level="ALL">

        <!-- 引入appender -->
        <!--<appender-ref ref="roll"/>-->
        <!--<appender-ref ref="consoleFilterAppender"/>-->
        <!--<appender-ref ref="consoleAppender"/>-->
        <appender-ref ref="asyncAppender"/>
    </root>

    <!--

        additivity="false"
        表示不继承rootlogger

    -->
    <logger name="com.bjpowernode" level="info" additivity="false">

        <!-- 在自定义logger中配置appender -->
        <appender-ref ref="consoleAppender"/>

    </logger>

</configuration>

SpringBoot日志实现

springboot框架在企业中的使用越来越普遍,springboot日志也是开发中常用的日志系统。springboot默认就是使用SLF4J作为日志门面,logback作为日志实现来记录日志。
额外添加 lombok 依赖 然后再使用 slf4j 会更加方便,以下是对比

一,直接使用slf4j
@Slf4j
@RestController
public class slf4jController {
Logger logger = LoggerFactory.getLogger(slf4jController.class);
@RequestMapping
public void test1(){
logger.info(“直接使用dlf4j情况下,打印日志”);
}
}
二,使用lombok依赖下的slf4j
@Slf4j
@RestController
public class slf4jController {
@RequestMapping
public void test1(){
log.info(“使用lombok情况下,打印日志”);
}
}

                 /*使用logback*/

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



                /*使用log4j2*/
                <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
                <!--排除掉原始依赖 以此去除logback引用-->
                <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>


                package com.bjpowernode.springbootlog;

        import org.apache.logging.log4j.LogManager;
        import org.junit.jupiter.api.Test;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.boot.test.context.SpringBootTest;

        @SpringBootTest
        class SpringbootlogApplicationTests {

            @Test
            public void test01() {

        /*

            入门案例:
                springboot日志具体实现
                    级别测试
                    默认是info级别
                    logback的风格输出(默认使用的是logback的日志实现)


         */
                Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
                logger.error("error信息");
                logger.warn("warn信息");
                logger.info("info信息");
                logger.debug("debug信息");
                logger.trace("trace信息");

            }

            @Test
            public void test02() {

        /*

            使用log4j2的日志实现
            观察桥接器是否起作用
            结果:仍然是slf4j+logback
            证明桥接器是起作用的

         */
                org.apache.logging.log4j.Logger logger = LogManager.getLogger(SpringbootlogApplicationTests.class);
                logger.info("info信息");


            }

            @Test
            public void test03() {

        /*

            application.properties(yml)是springboot的核心配置文件(用来简化开发使用)

            我们也可以通过该配置文件,修改日志相关的配置

         */
                Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
                logger.error("error信息");
                logger.warn("warn信息");
                logger.info("info信息");
                logger.debug("debug信息");
                logger.trace("trace信息");
            }

            @Test
            public void test04() {

        /*

            将日志输出到文件中
                使用logging.file.path来配置文件路径下的文件夹(logging.file直接配置文件的形式已经过时,不使用)
                在配置的文件夹下,日志文件生成的名字为spring.log

         */
                Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
                logger.error("error信息");
                logger.warn("warn信息");
                logger.info("info信息");
                logger.debug("debug信息");
                logger.trace("trace信息");
            }

            @Test
            public void test05() {

        /*

            如果是需要配置日志拆分等相对高级的功能
            那么application.properties就达不到需要了
            需要使用日志实现相应的配置文件

            例如我们现在使用的是logback日志实现
            那么就需要在类路径resources下,配置logback.xml

            由于log4j2性能的强大
            当今市场上越来越多的项目选择使用slf4j+log4j2的组合
            springboot默认使用的是slf4j+logback的组合
            我们可以将默认的logback替换成为log4j2

            1.启动器依赖,间接的依赖logback
                所以需要将之前的环境中,logback的依赖去除掉
            2.添加log4j2依赖
            3.将log4j2的配置文件log4j2.xml导入到类路径resources下面

         */

        /*Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);

        for (int i = 0; i < 2000; i++) {

            logger.error("error信息");
            logger.warn("warn信息");
            logger.info("info信息");
            logger.debug("debug信息");
            logger.trace("trace信息");

        }*/

                Logger logger = LoggerFactory.getLogger(SpringbootlogApplicationTests.class);
                logger.error("error信息");
                logger.warn("warn信息");
                logger.info("info信息");
                logger.debug("debug信息");
                logger.trace("trace信息");

            }


        }

application.properties

logging.level.com.bjpowernode=trace
logging.pattern.console=%d{yyyy-MM-dd} [%level] -%m%n
//日志输出地址
logging.file.path=D:/test/springbootlog

log4j2.xml

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

    <Appenders>

        <!-- 配置控制台输出 -->
        <Console name="consoleAppender" target="SYSTEM_ERR">

        </Console>

    </Appenders>


    <!-- 配置logger -->
    <Loggers>

        <Root level="trace">

            <AppenderRef ref="consoleAppender"/>
        </Root>

    </Loggers>

</Configuration>

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 name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">

        <target>
            System.err
        </target>

        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">

            <pattern>${pattern}</pattern>

        </encoder>

    </appender>


    <!-- 配置文件的appender 可拆分归档的文件 -->
    <appender name="roll" class="ch.qos.logback.core.rolling.RollingFileAppender">

        <!-- 输入格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!-- 引入文件位置 -->
        <file>D:/test/roll_logback.log</file>

        <!-- 指定拆分规则 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

            <!-- 按照时间和压缩格式声明文件名 压缩格式gz -->
            <fileNamePattern>D:/test/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>

            <!-- 按照文件大小来进行拆分 -->
            <maxFileSize>1KB</maxFileSize>

        </rollingPolicy>

    </appender>

    <logger name="com.bjpowernode" level="info" additivity="false">

        <appender-ref ref="roll"/>

    </logger>

</configuration>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值