vlambda博客
学习文章列表

你可能需要了解的 logback

logback

体系架构

附着器和布局

Logback 允许将日志记录请求打印到不同的目的地,在 Logback 中,打印输出的目的地称作附着器(Appender)。目前,支持的附着器有控制台、文件、远程 socket 服务器、数据库、JMS、以及远程 UNIX 系统日志。

此外,用户可以通过将布局(Layout)与附着器相关联来自定义日志的输出格式。布局负责根据用户的意愿格式化日志记录请求,而附着器负责将格式化的输出发送到目的地。布局模式(PatternLayout)是标准的 logback 的一部分,让用户根据类似于 C语言 printf 函数的转换模式来指定输出格式。

注意:日志事件格式化,将逐渐被 Encoder 接口替代;

encoder

考虑到 layout 在日志事件写出时不能控制日志事件,不能将日志事件批量聚合。与之相反的是,encoder 不但可以完全控制字节写出时的格式,而且还可以控制这些字节什么时候被写出。

直到 logback 的 0.9.19 版本,许多 appender 依赖 layout 实例去控制日志的格式化输出。因为基于 layout 接口存在了大量的代码,所以我们需要一种方式兼容 encoder 与 layout 进行交互。LayoutWrappingEncoder 就是 encoder 与 layout 之间的桥梁。它实现了 encoder 接口并且包裹了一个 layout,通过委托该 layout 将日志事件转换为字符串。

logger context

各个 logger 都被关联到一个 LoggerContext,LoggerContext 负责制造 logger,也负责以树结构排列各 logger。其他所有 logger 也通过 org.slf4j.LoggerFactory 类的静态方法 getLogger 取得。getLogger 方法以 logger 名称为参数。用同一名字调用 LoggerFactory.getLogger 方法所得到的永远都是同一个 logger 对象的引用。

执行流程

以记录器调用 info() 方法为例,分析 logback 的执行步骤。

1. 获取过滤器链

如果存在过滤器链,将会调用相应的方法进行过滤。如果过滤器链的返回值是FilterReply.DENY,则日志记录请求被丢弃;如果返回值是FilterReply.NEUTRAL,则进入下一步,步骤2;如果返回值是FilterReply.ACCEPT,则跳过下一步,直接进入步骤3。

2. 运用基础选择规则

在该步中,logback 比较日志记录器的有效级别与日志请求的级别,如果请求级别未启用,则丢弃请求并停止继续处理,否则进入下一步。例如,如果 L 是一个 logger 实例,那么,语句 L.info("..") 是一条级别为 INFO 的记录语句。记录请求的级别在高于或等于其 logger 的有效级别时被称为被启用,否则,称为被禁用。记录请求级别为 p,其 logger 的有效级别为 q,只有则当 p>=q时,该请求才会被执行。

该规则是 logback 的核心。级别排序为:TRACE < DEBUG < INFO < WARN < ERROR。

3. 创建 LoggingEvent 对象

创建 LoggingEvent 对象,包含日志记录请求的所有相关参数,如请求的日志记录器、请求级别、消息本身、可能已经传递的异常、当前时间、当前线程、发出日志请求的类的各种数据以及 MappedDiagnosticContext。

4. 调用附着器

创建 LoggingEvent 对象后,logback 会对所有可用的附着器调用doAppend()方法。随 logback 一起发布的所有附着器都扩展了 AppenderBase 抽象类,并以同步的方式实现了doAppend()方法确保线程安全。如果自定义过滤器存在的话, AppenderBase 的doAppend()方法也会调用连接到 Appender 的自定义过滤器。

5. 格式化输出

被调用的附着器负责格式化日志事件对象,一些(并非全部)附着器将格式化的任务委托给布局对象。布局格式化 LoggingEvent 实例并将结果作为字符串返回。有些附着器,如 SocketAppender ,不需要将日志时间对象转化为字符串,而是将其序列化,因此它们没有或者不需要布局。

6. 发送 LoggingEvent

日志记录事件在完全格式化之后,被各自的附着器发送到目的地。

UML 时序图

img "UML 时序图"

常用配置说明

<?xml version="1.0" encoding="UTF-8"?>
<!--
-scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
-scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
-           当scan为true时,此属性生效。默认的时间间隔为1分钟
-debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-
- configuration 子节点为 appender、logger、root
-->

<configuration scan="true" scanPeriod="60 second" debug="false">

    <!-- 负责写日志,控制台日志 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

        <!-- 一是把日志信息转换成字节数组,二是把字节数组写入到输出流 -->
        <encoder>
            <Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%5level] [%thread] %logger{0} %msg%n</Pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 文件日志 -->
    <appender name="DEBUG" class="ch.qos.logback.core.FileAppender">
        <file>debug.log</file>
        <!-- append: true,日志被追加到文件结尾; false,清空现存文件;默认是true -->
        <append>true</append>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%5level] [%thread] %logger{0} %msg%n</Pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>info.log</File>

        <!-- ThresholdFilter:临界值过滤器,过滤掉 TRACE 和 DEBUG 级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>

        <encoder>
            <Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%5level] [%thread] %logger{0} %msg%n</Pattern>
            <charset>UTF-8</charset>
        </encoder>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天生成一个日志文件,保存30天的日志文件
            - 如果隔一段时间没有输出日志,前面过期的日志不会被删除,只有再重新打印日志的时候,会触发删除过期日志的操作。
            -->

            <fileNamePattern>info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </TimeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender >

    <!--<!– 异常日志输出 –>-->
    <!--<appender name="EXCEPTION" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
        <!--<file>exception.log</file>-->
        <!--<!– 求值过滤器,评估、鉴别日志是否符合指定条件. 需要额外的两个JAR包,commons-compiler.jar和janino.jar –>-->
        <!--<filter class="ch.qos.logback.core.filter.EvaluatorFilter">-->
            <!--<!– 默认为 ch.qos.logback.classic.boolex.JaninoEventEvaluator –>-->
            <!--<evaluator>-->
                <!--<!– 过滤掉所有日志消息中不包含"Exception"字符串的日志 –>-->
                <!--<expression>return message.contains("Exception");</expression>-->
            <!--</evaluator>-->
            <!--<OnMatch>ACCEPT</OnMatch>-->
            <!--<OnMismatch>DENY</OnMismatch>-->
        <!--</filter>-->

        <!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
            <!--<!– 触发节点,按固定文件大小生成,超过5M,生成新的日志文件 –>-->
            <!--<maxFileSize>5MB</maxFileSize>-->
        <!--</triggeringPolicy>-->
    <!--</appender>-->

    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>error.log</file>

        <encoder>
            <Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%5level] [%thread] %logger{0} %msg%n</Pattern>
            <charset>UTF-8</charset>
        </encoder>

        <!-- 按照固定窗口模式生成日志文件,当文件大于20MB时,生成新的日志文件。
        -    窗口大小是1到3,当保存了3个归档文件后,将覆盖最早的日志。
        -    可以指定文件压缩选项
        -->

        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>error.%d{yyyy-MM}(%i).log.zip</fileNamePattern>
            <minIndex>1</minIndex>
            <maxIndex>3</maxIndex>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- 异步输出 -->
    <appender name ="ASYNC" class"ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold >0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref ="ERROR"/>
    </appender>

    <!--
    - 1.name:包名或类名,用来指定受此logger约束的某一个包或者具体的某一个类
    - 2.未设置打印级别,所以继承他的上级<root>的日志级别“DEBUG”
    - 3.未设置additivity,默认为true,将此logger的打印信息向上级传递;
    - 4.未设置appender,此logger本身不打印任何信息,级别为“DEBUG”及大于“DEBUG”的日志信息传递给root,
    -  root接到下级传递的信息,交给已经配置好的名为“STDOUT”的appender处理,“STDOUT”appender将信息打印到控制台;
    -->

    <logger name="ch.qos.logback" />

    <!--
    - 1.将级别为“INFO”及大于“INFO”的日志信息交给此logger指定的名为“STDOUT”的appender处理,在控制台中打出日志,
    -   不再向次logger的上级 <logger name="logback"/> 传递打印信息
    - 2.level:设置打印级别(TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF),还有一个特殊值INHERITED或者同义词NULL,代表强制执行上级的级别。
    -        如果未设置此属性,那么当前logger将会继承上级的级别。
    - 3.additivity:为false,表示此logger的打印信息不再向上级传递,如果设置为true,会打印两次
    - 4.appender-ref:指定了名字为"STDOUT"的appender。
    -->

    <logger name="com.weizhi.common.LogMain" level="INFO" additivity="false">
        <appender-ref ref="STDOUT"/>
        <!--<appender-ref ref="DEBUG"/>-->
        <!--<appender-ref ref="EXCEPTION"/>-->
        <!--<appender-ref ref="INFO"/>-->
        <!--<appender-ref ref="ERROR"/>-->
        <appender-ref ref="ASYNC"/>
    </logger>

    <!--
    - 根logger
    - level:设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。
    - 默认是DEBUG。
    - appender-ref:可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
    -->

    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <!--<appender-ref ref="DEBUG"/>-->
        <!--<appender-ref ref="EXCEPTION"/>-->
        <!--<appender-ref ref="INFO"/>-->
        <appender-ref ref="ASYNC"/>
    </root>
</configuration>

常用优化方案

  1. 日志分级输出文件
  2. 时间/大小滚动输出
  3. 异步输出
  4. 参数化日志记录

生产环境实用功能

  1. 自动重新加载配置文件,当配置文件修改了,能自动重新加载配置文件
  2. 多环境不同配置
  3. 根据条件调整日志级别,如alice这个用户登录,她的日志将打在DEBUG级别而其他用户可以继续打在WARN级别。要实现这个功能只需加4行XML配置。可以参考MDCFfilter

参考文档

  1. logback 的使用和 logback.xml 详解 : https://www.cnblogs.com/ryelqy/p/10314147.html
  2. Logback 体系架构 : https://blog.csdn.net/jiangqiongfeng/article/details/80356764
  3. 一个著名的日志系统是怎么设计出来的?:
  4. logback 基本架构和运作逻辑以及一些常用的自定义方式 : https://my.oschina.net/Aruforce/blog/2874042
  5. logback中文手册 : http://www.logback.cn/
- END -