暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Logback日志框架

java流水账 2020-03-03
750

微信公众号:Java流水账

本号记录国服安琪拉日常编程流水帐,欢迎后台留言

What -Logback是什么

Logback is intended as a successor to the popular log4j project, picking up where log4j leaves off.

Logback官方第一句就是Logback是用来替换log4j的, 请丢弃log4j。

Log4j的作者Ceki Gulcu 重写了Log4j,名字换成了Logback,所以Logback应该说是log4j的PLus版。Logback相信做JAVA后台开发的应该不陌生,这是一套日志系统,同样类型的日志系统还有:log4j、common-logging等。

阿里Java开发手册关于日志的第一条

【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架 SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

SLF4j是日志系统的门面,只提供接口,不提供实现。

图片来源:一个著名的日志系统是怎么设计出来的?

关于SLF4J门面模式的详细实现细节可以参考我的另一篇文章: 日志系统-SLF4J门面模式实现原理源码分析

Logback 被分成三个不同的模块:

  • logback-core

  • logback-classic

  • logback-access

Logback-core 是其它两个模块的基础。Logback-classic 模块可以看作是 log4j 的一个优化版本,它天然的支持 SLF4J,所以你可以随意的从其它日志框架(例如:log4j 或者 java.util.logging)切回到 logack。

Why -为什么用Logback

Logback brings a very large number of improvements over log4j, big and small. They are too many to enumerate exhaustively. Nevertheless, here is a non-exhaustive list of reasons for switching to logback from log4j. Keep in mind that logback is conceptually very similar to log4j as both projects were founded by the same developer. If you are already familiar with log4j, you will quickly feel at home using logback. If you like log4j, you will probably love logback.   -Logback官网

Logback相比较log4j有很多优势:

  • 更快的速度:重写了logback内部,使其在关键执行路径上的执行速度提高了大约十倍。Logback组件不仅速度更快,而且内存占用空间也较小。

  • 大量的测试:大量可靠性测试保证日志系统不干扰业务逻辑代码,稳定性比log4j 好。

  • 配置文件动态加载:修改了日志配置文件,不需要重启系统可以直接生效。

  • 从I/O故障中自动恢复 & 日志压缩, 这些操作都是异步进行,不影响正常业务

  • logback实现了log4j,所以如果你项目中依赖的SLF4J,直接把log4j直接替换成logback的jar包。

How -Logback如何用

第一趴: 使用Logback默认配置

1. 新建一个工程,maven添加slf4j、logback-classic

<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!--引入slf4j 门面-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.24</version>
</dependency>

<!--logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.11</version>
</dependency>
</dependencies>

2. 新建一个测试类,打印一行日志:

public class LogbackTest {

@Test
public void logback(){
Logger logger = LoggerFactory.getLogger(LogbackTest.class);

logger.info("test");
}
}

 3. 输出如下:

    17:05:08.444 [main] INFO com.study.LogbackTest - test

    为什么我们还没有添加配置文件,也能够正常输出日志,那是因为

    Logback按照如下流程加载配置:

    1. 检查系统环境变量是否设置了logback.configurationFile  可以通过👇方式设置

    java -Dlogback.configurationFile=/path/to/config.xml    APP

    如果指定了配置文件,就从ClassPath查找加载配置;

    2. 如果没有则按照下面的文件顺序查找配置文件

    • logback.groovy

    • logback-test.xml

    • logback.xml

    3. 如果以上配置文件都没有找到,会使用默认的ConsoleAppender输出。

    有兴趣可以看类ContextInitializer 代码:

    第5行查找加载配置文件,没有配置文件第20行使用默认配置

      //类 ContextInitializer
      public void autoConfig() throws JoranException {
      StatusListenerConfigHelper.installIfAsked(loggerContext);
              //这里查找加载1、2步的配置文件
      URL url = findURLOfDefaultConfigurationFile(true);
              //如果不为空 通过指定的配置文件开始配置
      if (url != null) {
      configureByResource(url);
              } else {
                 //使用默认配置 打印到控制台,使用默认格式(感兴趣可以自己看)
      BasicConfigurator basicConfigurator = new BasicConfigurator();
      basicConfigurator.setContext(loggerContext);
                  basicConfigurator.configure(loggerContext);
              }
      //类 ContextInitializer ==> findURLOfDefaultConfigurationFil函数(标注函数1)
      public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
      ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
      URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
      if (url != null) {
      return url;
      }
      //查询 GROOVY_AUTOCONFIG_FILE = "logback.groovy" 配置文件,加载
      url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
      if (url != null) {
      return url;
      }
      //查询 logback-test.xml 配置文件,加载
      url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
      if (url != null) {
      return url;
      }
      //录查询 logback.xml 配置文件,加载
      return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
      }
      }

      第二趴: 自定义Logback.xml 打印日志

      1. 在resource目录下新建一个logback.xml文件,配置信息如下:

        <?xml version="1.0" encoding="UTF-8" ?>
        <configuration scan="false" scanPeriod="10000" debug="true">
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </layout>
        </appender>


        <root level="info">
        <appender-ref ref="STDOUT" >
        </root>
        </configuration>

        2. 运行测试用例,打印如下:

          17:12:06,233 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
          17:12:06,233 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
          17:12:06,233 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/Users/zw/Documents/Code/logback_study/target/classes/logback.xml]
          17:12:06,324 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
          17:12:06,327 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
          17:12:06,359 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - This appender no longer admits a layout as a sub-component, set an encoder instead.
          17:12:06,359 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.
          17:12:06,359 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - See also http://logback.qos.ch/codes.html#layoutInsteadOfEncoder for details
          17:12:06,360 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to INFO
          17:12:06,360 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
          17:12:06,361 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
          17:12:06,361 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@68837a77 - Registering current configuration as safe fallback point
          2020-03-02 17:12:06.363 [main] INFO com.study.LogbackTest - test

          可以看到Console窗口输出很多Loggback内部流程

          1. 没有找到logback.groovy配置文件;

          2. 没有找到logback-test.xml配置文件

          3. 找到logback.xml配置文件;

          4. 初始化Appender并且命名为STDOUT;

          5. Appender不推荐使用layout作为子组件,建议使用encoder代替;

          6. 设置ROOT Logger的级别为Info,并且挂载名称为STDOUT的Appender;

          这是因为我们把配置文件debug模式打开了,logback内部流程日志输出了。

          <configuration  
          debug="true">

          另外可以设置:

          1. 开启配置文件自动重新加载

          2. 打印包信息,packagingData = true , 如下

            14:28:48.835 [btpool0-7] INFO  c.q.l.demo.prime.PrimeAction - 99 is not a valid value
            java.lang.Exception: 99 is invalid
            at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
            at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
            at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
              at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]

            这在解决包冲突的时候能有点用,生产环境建议关闭,性能损耗有点大。

            Configuration对应可以配置的属性有:

            • debug:  是否打印logback内部日志

            • scan:   是否开启配置文件扫描,日志配置文件更改动态更新

            • scanPeriod:定时检查配置文件更新时间间隔

            • packagingData:是否打印包信息

            Logback 的配置文件基本结构图如下:

            最基本的结构为 <configuration> 元素,包含 0 或多个 <appender> 元素,其后跟 0 或多个 <logger> 元素,其后再跟最多只能存在一个的 <root> 元素。

            第三趴: Logback组件

            Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。
            logback中文手册

            一. Logger组件

                    Logger是用来设置打印指定范围(应用/包/某个类)日志的组件。

                    配置中Logger标签的属性包括:

            • name:  通过LoggerFactory查找Logger的标示,一般为类的全限定名;

            • level:  Logger打印日志的最小级别,打印的日志级别 >= level

            • additivity:是否向父Logger传递日志,可选,默认为true

                 <logger> 元素至少包含 0 或多个 <appender-ref> 元素。

                 <root> 也是一个Logger,但是比较特殊,它的name已经限定了,level也设置为默认的debug,当然level可以在配置文件更改。


            来看一个例子:

            1. logback.xml配置level为 debug

              <?xml version="1.0" encoding="UTF-8" ?>
              <configuration scan="false" scanPeriod="10000" debug="true">


              <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
              <layout class="ch.qos.logback.classic.PatternLayout">
              <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
              </layout>
              </appender>


              <root level="debug">
              <appender-ref ref="STDOUT" >
              </root>


              </configuration>

              2. 测试代码

                @Test
                public void logback(){


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


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

                3.控制台输出如下:

                  2020-03-02 23:23:33.926 [main] DEBUG com.study.LogbackTest - =====debug=====
                  2020-03-02 23:23:33.929 [main] INFO com.study.LogbackTest - =====info=====
                  2020-03-02 23:23:33.929 [main] WARN com.study.LogbackTest - =====warn=====
                  2020-03-02 23:23:33.929 [main] ERROR com.study.LogbackTest - =====error=====

                  配置文件解释:Logback将日志级别 >= debug的日志交给name为STDOUT的Appender以Layout指定的格式输出到控制台(ConsoleAppender)


                  为了更好的理解Logger,

                  1. 我们把配置文件添加一个只有name="com"的logger节点,如下:

                    <?xml version="1.0" encoding="UTF-8" ?>
                    <configuration scan="false" scanPeriod="10000" debug="true">

                    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
                    <layout class="ch.qos.logback.classic.PatternLayout">
                    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
                    </layout>
                    </appender>


                    <logger name="com"/>


                    <root level="info">
                    <appender-ref ref="STDOUT" >
                    </root>

                    </configuration>

                    2. 添加一个类,在com.study包  如下

                      package com.study;
                      import org.slf4j.Logger;
                      import org.slf4j.LoggerFactory;


                      /**
                      * @author zw
                      * @date 2020/3/2.
                      */
                      public class SubLevelTest {
                          // Logger的 name = com.study.SubLevelTest
                      Logger logger = LoggerFactory.getLogger(SubLevelTest.class);


                      public void test(){
                      logger.debug("=====子Logger debug打印=====");


                      logger.info("=====子Logger info打印=====");


                      logger.error("=====子Logger error打印=====");
                      }
                      }

                      3. 控制台窗口

                        2020-03-02 23:37:46.010 [main] INFO  com.study.SubLevelTest - =====子Logger   info打印=====
                        2020-03-02 23:37:46.010 [main] ERROR com.study.SubLevelTest - =====子Logger   error打印=====

                        解释:新创建了一个name为com的Logger,它是所有以com开头Logger的父Logger,而root是所有Logger的父Logger,

                        1. com <logger>标签没有设置Appender,这个Logger没有挂载任何Appder打印日志;

                        2. com <logger>标签的additivity默认为true,日志会向上传递;传递到父logger(root)来打印日志;

                        3. 没有设置level,继承父Logger的级别,所以打印 >= info级别日志;


                        为了更好理解additivity这个属性,

                        我们改下例子,把com的logger标签additivity属性设置为false,如下

                          <logger name="com" level="info" additivity="false"/>


                          <root level="debug">
                          <appender-ref ref="STDOUT" >
                          </root>

                          控制台输出:

                            00:05:54,596 |-WARN in Logger[com.study.SubLevelTest] - No appenders present in context [defaultfor logger [com.study.SubLevelTest].

                            提示没有可用的Appender可以用于打印,证明com的日志不向上传递。

                            另外如果给com <logger>标签添加一个<appender>, additivity设置为true,日志会重复输出,因为自己的Appender和父Logger的Appdener都会输出。

                            有兴趣的可以自己试试。

                            Logback关键源代码:

                              // 我们代码中的LoggerFacotory就是LoggerContext,LoggerContext实现了ILoggerFactory接口
                              public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
                              / 根Logger
                              final Logger root;
                                  // 核心Map Logger logger = LoggerFactory.getLogger(LogbackTest.class);
                                  // key是 com.study.LogbackTest value是Logger
                                  // 因为会有多线程并发访问的问题,因此使用ConcurrentHashMap实例化
                                  private Map<String, Logger> loggerCache;


                              public LoggerContext() {
                              super();
                              this.loggerCache = new ConcurrentHashMap<String, Logger>();


                                      // ROOT Logger名称d
                              this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
                              /设置Logger级别
                              this.root.setLevel(Level.DEBUG);
                                      loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
                                 }
                              }


                              二. Appender组件

                                   Appender组件是用来写日志的,最核心的组件。

                              Appender通过<appender>元素进行配置:

                              • name: 用来指定 appender 的名字;

                              • class:  需要指定类的全限定名用于实例化。

                              Appender有很多种,前面我们演示的只是ConsoleAppender,还有FileAppender、RollingFileAppender等。类图如下:




                                             名称
                                               输出
                                              备注
                                 ConsoleAppender将日志事件输出到控制台通过 System.out来进行输出
                                      FileAppender
                              将日志事件输出到文件
                                RollingFileAppender轮转日志文件的功能在满足了特定的条件之后,将日志输出到另外一个文件。
                                   KafkaAppender
                              输出到kafka
                              这个是我自己加的,实际生产用的比较多


                              一般实际生产环境,可能会将日志推到kafka中,ELK集中管理。

                              后面专门会一篇文章介绍:SpringBoot环境配置Logback将日志推送到Kafka的文章,以及如何管理、打印日志的最佳实践。


                              下面是三种Logger Appender配置的例子:

                                <configuration>


                                <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" >
                                <!-- encoder 默认使用 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
                                <encoder>
                                <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
                                </encoder>
                                </appender>


                                <appender name="FILE" class="ch.qos.logback.core.FileAppender">
                                        <!-- 如果为 true,日志事件会被追加到文件中,否则的话,文件会被截断。默认为 true -->
                                        <append> true </true>
                                <file>testFile.log</file>
                                <!-- 将 immediateFlush 设置为 false 可以获得更高的日志吞吐量 -->
                                <immediateFlush>true</immediateFlush>
                                <!-- 默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
                                <encoder>
                                <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
                                </encoder>
                                </appender>


                                    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
                                        <!-- 输出文件-->
                                <file>./log/info.log</file>
                                <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                                <level>info</level>
                                </filter>
                                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                                <FileNamePattern>./log/info-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
                                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                                <maxFileSize>50MB</maxFileSize>
                                </timeBasedFileNamingAndTriggeringPolicy>
                                <MaxHistory>10</MaxHistory>
                                </rollingPolicy>
                                <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                                <pattern>
                                [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%level] %logger:%L x:\(%X\) - %msg%n
                                </pattern>
                                </encoder>
                                </appender>

                                <root level="DEBUG">
                                <appender-ref ref="STDOUT" >
                                </root>
                                </configuration>

                                三. layout & encode组件

                                    1. layout 是 logback 的组件,负责将日志事件转换为字符串。Layout 接口中的 format() 方法接受一个表示日志事件的对象 (任何类型) 并返回一个字符串。

                                    2. encoder 将日志事件转换为字节数组,同时将字节数组写入到一个 OutputStream
                                 中。

                                •     在之前的版本中,大多数的 appender 依赖 layout 将日志事件转换为 string,然后再通过 java.io.Writer
                                   写出。在之前的版本中,用户需要在 FileAppender
                                   中内置一个 PatternLayout

                                •     在 0.9.19 之后的版本中,FileAppender
                                   以及子类需要一个 encoder 而不是 layout

                                  <!--  旧配置-->
                                  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
                                  <file>testFile.log</file>
                                  ...
                                  <layout class="ch.qos.logback.classic.PatternLayout">
                                  <pattern>%msg%n</pattern>
                                  </layout>
                                  </appender>


                                  <!-- 新配置-->
                                  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
                                  <file>testFile.log</file>
                                  ...
                                  <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                                  <pattern>%msg%n</pattern>
                                  </encoder>
                                  </appender>

                                  原因:

                                  • Layout 它只能将日志事件转换为成 string。而且,考虑到 layout 在日志事件写出时不能控制日志事件,不能将日志事件批量聚合。

                                  • 与之相反的是,encoder 不但可以完全控制字节写出时的格式,而且还可以控制这些字节什么时候被写出。

                                  PatternLayoutEncoder
                                   是目前真正唯一有用的 encoder。

                                  最后留一个小问题

                                  我们执行下代码,“test”在logback内部是怎么流转的呢?什么时候发送,怎么序列化?立即发送还是放延迟队列?同一条日志不同的appender不同的layout是否会发送多次?


                                  logger.info("test");

                                  如下,是执行的时序图:


                                  下一篇文章: SpringBoot 整合Logback

                                  ----------------------------国服安琪拉------------------------------------

                                  这里没有敷衍的复制粘贴,博眼球的面试资料分享,有的只是尽可能清晰的讲清个人开发中遇到的一个个问题和总结。欢迎大家关注Java流水账,纯粹的个人技术公众号。



                                  文章转载自java流水账,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                  评论