Logback日志框架

Logback日志框架

1、Logback入门案例

1、Logback简介

官方网站:https://logback.qos.ch/index.html、Maven 仓库地址:https://search.maven.org/artifact/ch.qos.logback/logback-classic

Logback日志框架的优势:

  • Logback 是 log4j 框架的作者开发的新一代日志框架(性能比Log4j要好),它效率更高、能够适应诸多的运行环境,同时天然支持SLF4J
  • Logback的定制性更加灵活,同时也是SpringBoot的内置日志框架

Logback主要分为三个模块:

  • logback-core:其它两个模块的基础模块,也是最核心的模块
  • logback-classic:它是Log4j的一个改良版本,同时它完整实现了SLF4J API。使你可以方便的更换成其它日志框架(Log4j、JUL)
  • logback-access:访问模块与Servlet容器集成提供通过HTTP来访问日志的功能

2、Logback示例

<!--使用slf4j日志门面,实际上logback默认就自带slf4j依赖,不引入当前依赖也可以-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<!--logback的基础模块,实际上也可以不导入,因为logback-classic中也包含了基础模块包-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>

<!--实际可以只导入logback-classic包即可-->
<!--导入logback-classic依赖,实际上当前依赖已经包含了slf4j-api及logback-core,所以导入此依赖就够了-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LogbackTest.class);
        logger.error("error test");
        logger.warn("warn test");
        logger.info("info {}", "test");
        logger.debug("debug test ");
        logger.trace("trace test");
    }
}
17:12:38.837 [main] ERROR LogbackTest - error test
17:12:38.840 [main] WARN LogbackTest - warn test
17:12:38.840 [main] INFO LogbackTest - info test
17:12:38.841 [main] DEBUG LogbackTest - debug test 

源码分析:其实我们发现即使项目中没有引入slf4j我们这里也是用的slf4j门面进行编程,下面我们查看源码及依赖传递:

1、可以查看到Logback中的Logger继承了org.slf4j.Logger

package ch.qos.logback.classic;
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {}

2、从logback的pom依赖中我们看到slf4-api及logback-core,依赖会进行传递

<!--只贴出了关键部分--><
parent>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-parent</artifactId>
    <version>1.2.3</version>
</parent>
<artifactId>logback-classic</artifactId>
<dependencies>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

2、组件介绍及配置

1、几大组件介绍

  1. Logger:日志的记录器,主要用于存放日志对象,也可以定义日志类型、级别
  2. Appender:输出源,用于指定日志输出的目的地(可以是控制台、文件、数据库 等等)
  3. Encoder:与Layout一样,用来格式化日志信息输出,把事件转换成字符串。注意:在logback 0.9.19 版之前没有 encoder
  4. Layout:在Logback中Layout对象被封装在Encoder中,Encoder等于Layout。所以一般使用的都是encoder
  5. Level:是用来定义日志级别的(默认是:DEBUG)
  6. Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过
  7. LoggerContext:各个logger都被关联到一个LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各logger。其他所有logger也通过org.slf4j.LoggerFactory 类的静态方法getLogger取得。 getLogger方法以 logger名称为参数。用同一名字调用LoggerFactory.getLogger 方法所得到的永远都是同一个logger对象的引用

Appender :Logback 将写入日志事件的任务委托给一个名为 Appender 的组件。Appender 必须实现 ch.qos.logback.core.Appender 接口,Appender组件用来定义日志输出格式,日志如何过滤以及日志文件的处理。appender元素可以包含零个或一个layout元素,零个或多个encoder元素以及零个或多个filter元素。

Encoder:一个appender有一个encoder,负责将一个event事件转换成一组byte数组,并将转换后的字节数据输出到文件中。Encoder负责把事件转换为字节数组,并把字节数组写到合适的输出流。因此,encoder可以控制在什么时候、把什么样的字节数组写入到其拥有者维护的输出流中。Encoder接口有两个实现类,LayoutWrappingEncoder与PatternLayoutEncoder。注意:在logback 0.9.19 版之前没有 encoder。在之前的版本里,多数 appender 依靠 layout 来把事件转换成字符串并用java.io.Writer 把字符串输出。在之前的版本里,用户需要在FileAppender里嵌入一个 PatternLayout。

Level:Logback日志级别如下(默认级别:DEBUG):

// ERROR(错误信息) > WARN(警告信息) > INFO(重要信息) > DEBUG(普通信息) > TRACE(追踪信息) DEBUG为默认的打印级别
public static final Integer OFF_INTEGER = 2147483647;  // OFF:最高等级的而级别,用于关闭所有的日志记录
public static final Integer ERROR_INTEGER = 40000;     // ERROR:记录错误信息,不会影响系统运行。一般不想输出太多日志,则使用该级别即可
public static final Integer WARN_INTEGER = 30000;      // WARN:记录警告信息
public static final Integer INFO_INTEGER = 20000;      // INFO:记录运行信息
public static final Integer DEBUG_INTEGER = 10000;     // DEBUG:(默认)记录调式信息,一般在开发中使用
public static final Integer TRACE_INTEGER = 5000;      // TRACE:记录追踪信息,记录程序的流程信息,一般情况不会使用
public static final Integer ALL_INTEGER = -2147483648; // ALL:最低等级,用于打开所有级别的日志记录

LoggerContext:可以在代码中直接获取LoggerContext,通过LoggerContext可以动态修改Logback的配置

import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
// 根据包路径或类路径获取日志记录器,"ROOT" 表示根记录器: Logger getLogger(final String name);
loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.toLevel("error"));
// 停止logback-classic,也就是停止Logback
loggerContext.stop();

2、配置文件加载

Logback提供了三种种配置文件:logback.groovy、logback-test.xml、logback.xml(位于src/main/resources目录下)如果都不存在则采用默认的配置

  1. Logback首先会试着查找logback.groovy文件
  2. 当没有找到时,继续试着查找logback-test.xml文件
  3. 当没有找到时,继续试着查找logback.xml文件
  4. 如果仍然没有找到,则使用默认配置(打印到控制台)
  5. 其加载顺序为(优先级从高到低):logback.groovy =》 logback-test.xml =》 logback.xml

对XML日志文件的配置,大多数人第一次接触时有一种望而生畏的感觉,其实如果仔细分析,会发现核心的部分只有三个元素:appender、logger、root

                              ┌── 日志输出格式
               ┌───appender───┼── 日志过滤
               │              └── 日志文件处理
               │              ┌── 获取哪些(包)的日志
               ├────logger────┤                     	──┐
               │              └── 日志的输出级别            ├ 日志组件(root,logger)
               │              ┌── 根节点                 ──┘
configuration──┼─────root─────┤
               │              └── 只有level属性
               │
               │─────property──── 定义变量(非必须)
               │
               └───contextName─── 应用上下文名称(非必须)

3、XML配置模板

模板 1:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
                 当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<!--
总体说明:根节点下有2个属性,三个节点
属性: contextName 上下文名称; property 设置变量
节点: appender,  root, logger
-->
<configuration scan="true" scanPeriod="10 seconds">
    <!--
           contextName说明:
           每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,
           用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。
         -->
    <contextName>logback-spring</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
    <property name="logging.path" value="myLogs"/>

    <!--0. 日志格式和颜色渲染 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr"
                    converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <!--1. 输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>

        <!--日志文档输出格式-->
        <encoder>
            <!--指定日志格式-->
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!--设置字符集-->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文档-->
    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名~~~~~file设置打印的文件的路径及文件名,建议绝对路径-->
        <file>${logging.path}/web_debug.log</file>

        <!--日志文档输出格式-->
        <encoder>
            <!--指定日志格式-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>

        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <!--
            日志记录器的滚动策略
            SizeAndTimeBasedRollingPolicy 按日期,大小记录日志
            另外一种方式:
                rollingPolicy的class设置为ch.qos.logback.core.rolling.TimeBasedRollingPolicy

        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <!--
                归档的日志文件的路径,例如今天是2018-08-23日志,当前写的日志文件路径为file节点指定,
                可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
                而2018-08-23的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引
             -->
            <fileNamePattern>${logging.path}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>

            <!--
               配置日志文件不能超过100M,若超过100M,日志文件会以索引0开始,命名日志文件
               例如error.20180823.0.txt
               -->
            <timeBasedFileNamingAndTriggeringPolicy
                                                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>

            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>

        <!-- 此日志文档只记录debug级别的 -->
        <!-- 过滤策略:
            LevelFilter : 只打印level标签设置的日志级别
            ThresholdFilter:打印大于等于level标签设置的级别,小的舍弃
         -->
        <!--<filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的日志级别 -->
            <level>debug</level>
            <!--匹配到就允许-->
            <onMatch>ACCEPT</onMatch>
            <!--没有匹配到就禁止-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 2.2 level为 INFO 日志,时间滚动输出  -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${logging.path}/web_info.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${logging.path}/web-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                                                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 2.3 level为 WARN 日志,时间滚动输出  -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${logging.path}/web_warn.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logging.path}/web-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                                                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 2.4 level为 ERROR 日志,时间滚动输出  -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${logging.path}/web_error.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logging.path}/web-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                                                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
      以及指定<appender>。<logger>仅有一个name属性,
      一个可选的level和一个可选的addtivity属性。
      name:用来指定受此logger约束的某一个包或者具体的某一个类。
      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
            还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
            如果未设置此属性,那么当前logger将会继承上级的级别。
      addtivity:是否向上级logger传递打印信息。默认是true。
      <logger name="org.springframework.web" level="info"/>
      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
  -->

    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
        【logging.level.org.mybatis=debug logging.level.dao=debug】
     -->

    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->

    <!-- 4. 最终的策略 -->
    <!-- 4.1 开发环境:打印控制台-->
    <!-- <springProfile name="dev">
        <logger name="com.cic.analysis.business.dao" level="debug"/>&lt;!&ndash; 修改此处扫描包名 &ndash;&gt;
    </springProfile>-->

    <!--
        root指定最基础的日志输出级别,level属性指定
        appender-ref标识的appender将会添加到这个logger
    -->
    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="DEBUG_FILE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>

    <!-- 4.2 生产环境:输出到文档
    <springProfile name="pro">
        <root level="info">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile> -->

</configuration>

模板 2:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 属性描述 scan:设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志文件 输入位置 -->
    <property name="log_dir" value="logs/" />
    <!-- 日志最大的历史 30天 -->
    <property name="maxHistory" value="30"/>

    <!-- ConsoleAppender 控制台输出日志 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 对日志进行格式化 -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
        </encoder>
    </appender>

    <!-- ERROR级别日志 -->
    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤器,只记录ERROR级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志输出位置  可相对、和绝对路径 -->
            <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/error-log.log</fileNamePattern>
            <!-- 最大保存30天-->
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- WARN级别日志 appender -->
    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤器,只记录WARN级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/warn-log.log
            </fileNamePattern>
            <!-- 日志最大的历史 30天 -->
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- INFO级别日志 appender -->
    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤器,只记录INFO级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/info-log.log
            </fileNamePattern>
            <!-- 日志最大的历史 30天 -->
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="DruidFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${log_dir}/settle-query.log-druid-%d{yyyy-MM-dd}</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>90</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <!-- DEBUG级别日志 appender -->
    <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤器,只记录DEBUG级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/debug-log.log
            </fileNamePattern>
            <!-- 日志最大的历史 30天 -->
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- TRACE级别日志 appender -->
    <appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤器,只记录ERROR级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>TRACE</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/trace-log.log
            </fileNamePattern>
            <!-- 日志最大的历史 30天 -->
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 指定其他logger的Appender -->
    <logger name="org.springframework" value="WARN" />
    <logger name="org.apache" value="WARN" />
    <logger name="com.netflix" value="WARN" />
    <logger name="me.chanjar" value="WARN" />
    <logger name="com.ulisesbocchio" value="WARN" />
    <logger name="org.hibernate" value="WARN" />
    <logger name="com.github.liuweijw" value="INFO" />
    <logger name="c.n.discovery" value="WARN" />
    <logger name="o.s.c.s" value="WARN" />
    <logger name="c.u.j.encryptor" value="WARN" />
    <logger name="o.s.boot" value="WARN" />
    <!-- 指定druid的日志级别为DEBUG,除了继承rootLogger的appender外,自己还加了DruidFILE -->
    <logger name="druid" level="DEBUG">
        <appender-ref ref="DruidFILE" />
    </logger>
    <!-- 指定rootLogger级别   INFO,appender为STDOUT,ERROR,INFO-->
    <root level="INFO">
        <!-- 控制台输出 -->
        <appender-ref ref="STDOUT" />
        <!-- 文件输出 -->
        <appender-ref ref="ERROR" />
        <appender-ref ref="INFO" />
    </root>
</configuration>

3、配置文件结构详解

Logback的配置,需要配置输出源Appender,打日志的Logger(子节点)和Root(根节点)实际上,它输出日志是从子节点开始,子节点如果有输出源直接输出,如果无,判断配置的additivity,是否向上级传递,即是否向root传递,传递则采用Root的输出源,否则不输出日志。

logback.xml配置文件根结点为configuration,内主要包含contextName、property、appender、filter、pattern、logger、root 等结点。基本结构可以描述为1个configuration元素,包含零个或多个appender元素,后跟零个或多个logger元素,后跟最多1个root元素(也可以没有)

01、configuration 根元素

根元素configuration有三个属性:debug、 scan、scanPeriod,及一个特殊的属性:packagingData,示例及解释如下:

  • debug:默认为false,若设置为true,则打印出logback内部运行日志信息
  • scan:默认值为true,若设置为true,配置文件如果发生改变,将会自动重新加载配置文件
  • scanPeriod:与scan配合使用,当scan为true时,此属性生效,默认的时间间隔为1分钟,设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。可以设置为scanPeriod="30 seconds"每30秒检测一次。单位有:milliseconds、seconds、munites、hours,默认为milliseconds
    注意:如果是在本地 IDE 编辑器中测试,注意修改的是 classes 编译目录下的 logback.xml 文件,而不是 resources 目录下的源文件(一般不用)
  • packagingData:在堆栈跟踪中启用包数据。注意从版本1.1.4开始,包装数据默认为禁用
<?xml version="1.0" encoding="UTF-8"?>
<!-- debug : 开启logback运行日志输出,true为始终输出,false为出错时才输出-->
<!-- scan :开启"热更新"-->
<!-- scanPeriod:"热更新"扫描周期,默认 60 seconds(60秒) 单位有:milliseconds、seconds、munites、hours,默认为milliseconds-->
<configuration debug="true" scan="true" scanPeriod="60 seconds">
</configuration>    

开启packagingDatas配置:可按如下配置开启包数据(配置文件或者代码形式取其一即可):

<configuration packagingData="true">
</configuration>
import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.setPackagingDataEnabled(true);

如果开启了,logback 会在输出的堆栈行中显示它是属于哪个 jar 或者哪个类的。此信息由 jar 文件的名称和版本组成,表明堆栈信息来源于此。此机制对于识别软件版本问题非常有用。但是,计算成本相当昂贵,尤其是在经常引发异常的应用程序中。以下演示开启的结果,即多了 [] 括号内的信息。

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]
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
  at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
  at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
  at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]

02、contextName 元素

日志上下文名称:contextName,每个日志组件(logger)都会关联到日志上下文,默认上下文名称是:default,用于标识应用,如果多个应用输出到同一个地方,就有必要使用%contextName来区别。contextName直接在configuration元素下:

<configuration>
    <!--定义后,在其他property属性或appender中便可通过”%contextName”来获取和使用该上下文名称了-->
    <contextName>HelloWorld-log</contextName>
</configuration>

03、property 属性元素

1、用来定义变量值的标签,通过property定义的值会被插入到logger上下文中。property标签中有: name、value 两个属性,属性含义如下:

  • name:值是变量的名称
  • value:值是变量定义的值
  • 定义变量后,可以使${name}来使用变量
<configuration>
    <!--定义变量-->
    <property name="logName" value="logbackStudy" />
    <!--从src/main/resources加载配置文件(文件内key=val形式每行一个), 配置文件中的值可直接使用-->
    <property resource="logback.properties" />
    <!--file获取绝对路径-->
    <property file="D:\\logback.properties" />
</configuration>

2、如果是在Spring或SpringBoot项目当中,想让value值是通过配置文件获取,可使用springProperty来定义。这里是获取项目名称和运行的服务器IP

  • scope:作用域
  • name:名称,在下面引用的时候的名字
  • source:在application.properties文件中的key
  • defaultValue:默认值,该属性在application.properties中未设置时,会使用默认值
<!--其中source指定的spring.application.name便是在application.properties当中配置变量。此配置还是比较常用的,可以做到灵活区分环境-->
<springProperty scope="context" name="appName" source="spring.application.name" />
<springProperty scope="context" name="ip" source="spring.cloud.client.ipAddress" />

3、变量作用域(scope)

定义的变量是有作用域的,如本地作用域,上下文作用域,系统级作用域。默认是本地作用域。

  1. Local Scope(本地作用域):从配置文件中定义的本地变量即在本地配置文件使用。每次解析和执行配置文件时,都会重新定义本地作用域中的变量
  2. Context Scope(上下文作用域):一个拥有上下文作用域的变量存在于上下文中,于上下文共存,直到被清除。在所有记录事件中都可用到,包括那些通过序列化发送到远程主机的事件
  3. System Scope(系统级作用域):系统级作用域的变量被插入到JVM的系统属性中,生命周期和JVM一致,直到被清除
  4. 在进行属性替换时,查找变量的顺序为:local,context,system

04、timestamp 时间元素

获取时间戳字符串并格式化:timestamp。他有两个属性key和datePattern

  1. key:标识timestamp的名字
  2. datePattern:设置将当前时间(解析配置文件的时间)转换为字符串的模式,遵循java.txt.SimpleDateFormat的格式
<configuration>
    <!-- 实际上也是定义成变量给后面使用,与property类似 -->
    <timestamp key="nowDate" datePattern="yyyyMMdd" />
    <timestamp key="nowDateTime" datePattern="yyyyMMdd_HHmmss" />
    <contextName>${nowDateTime}</contextName>  
</configuration>

05、conversionRule 元素

主要用来配置转换器,可以自定义Pattern模板,也可以引入一些其他模板来解析Pattern,比如打印彩色日志,打印IP地址。conversionRule 元素中有两个参数,解释如下:

  • conversionWord:在Pattern中的关键字
  • converterClass:转换器类
<!-- 彩色日志,需要在idea中安装grep console插件 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />

<!-- 彩色日志格式 -->
<property name="COLOR_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%p) %clr(${PID}){magenta} %clr([%.10t]){yellow} %clr([%c{1}]){blue}%clr([%L]){faint}%X{traceId} %m%n"/>

上面%clr就是要用ColorConverter来解析,后面{blue}就是要展示的颜色。

06、appender 日志输出元素

Appender 的介绍

官方文档为:https://logback.qos.ch/manual/appenders.html

Appender 的组件有两个属性:name和class,并且需要强制赋值,解释如下:

  • name:定义写日志组件的唯一名称,可以随便定义,后面使用该appender是也是通过名称来指定
  • class:指定要实例化的appender类的完全限定名称

Appender种类有很多,常用的种类有:ConsoleAppender(输出到控制台)、FileAppender(输出到文件)、SMTPAppender(输出到邮件)、DBAppender(输出到数据库)、AsyncAppender(异步输出,包装其它具体的appender,不单独使用)等,如下介绍常用的

  • ConsoleAppender:日志输出到控制台,类名:ch.qos.logback.core.ConsoleAppender
  • FileAppender:日志输入到文件,类名:ch.qos.logback.core.FileAppender
  • RollingFileAppender:滚动记录文件(FileAppender的子类)当符合条件(大小、时间)日志进行切分处理。类名:ch.qos.logback.core.rolling.RollingFileAppender
  • 实战中ConsoleAppender及RollingFileAppender使用较多,若需要自定义如把日志输出到消息队列,可以自定义实现AppenderBase接口

除了上述Appender外,另外还有SocketAppender(输出到socket)、SMTPAppender(输出到邮件)、DBAppender(输出到数据库)等具体使用查文档

ConsoleAppender

ConsoleAppender 就跟名字显示的一样,是将日志事件附加到控制台,进一步说就是通过 System.out(默认) 或 System.err 来进行输出。ConsoleAppender 通过用户指定的 encoder,格式化日志事件。

属性名类型描述
targetStringSystem.out 或 System.err。默认为 System.out
encoderEncoder决定通过哪种方式将事件写入 OutputStreamAppender
withJansiboolean默认值为 false。设为 true 可以激活 Jansi 在 windows 使用 ANSI 彩色代码。
在 windows 上如果设置为 true,你应该将 org.fusesource.jansi:jansi:1.9 这个 jar 包放到 classpath 下。
基于 Unix 实现的操作系统,像 Linux、Max OS X 都默认支持 ANSI 才彩色代码。
<configuration>
    <!--日志输出器:输出到控制台-->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象, 默认为 System.out 是黑色字体, System.err 是红色字体 -->
        <target>System.out</target>
        <!-- 输出的日志消息格式配置, encoder 默认使用 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d [%t] [%-5level] %c %M %L %m %n</pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 根记录器 -->
    <root level="INFO">
        <!--输出记录的日志-->
        <appender-ref ref="consoleAppender"/>
    </root>
</configuration>
FileAppender

FileAppender是OutputStreamAppender的子类,将日志事件输出到文件中。通过file来指定目标文件。如果该文件存在,根据append的值,要么将日志追加到文件中,要么该文件被截断。

属性名类型描述
fileString要写入文件的名称。如果文件不存在,则新建
encoderEncoder参见 ConsoleAppender 的属性
appendboolean如果为 true,日志事件会被追加到文件中,否则的话,清空现存文件。默认为 true
prudentboolean在严格模式下,FileAppender会将日志安全的写入指定文件。即使在不同的 JVM 或者不同的主机上运行 FileAppender 实例。默认值为 false。严格模式可以与 RollingFileAppender 结合使用

1、将日志输出到文件:FileAppender。包含file(文件名)、encoder(格式化)元素

<configuration>
    <!--定义变量, 通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量-->
    <property name="pattern" value="%d [%t] [%-5level] %c %M %L %m %n"></property>
    <property name="log_file" value="./logs"></property>
    <!-- 通过 "bySecond" 将时间格式化成 "yyyyMMdd'T'HHmmss" 的形式插入到 logger 的上下文中这个值对后续的配置也适用-->
    <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss" />

    <!--日志输出器:输出到文件-->
    <appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
        <!-- 利用之前创建的 timestamp 来创建唯一的文件, 生成的文件名: log-20220402T122403.log -->
        <file>${log_file}/logback-${bySecond}.log</file>
        <!-- 将 immediateFlush 设置为 false 可以获得更高的日志吞吐量 -->
        <immediateFlush>true</immediateFlush>
        <!-- 输出的日志消息格式配置, encoder 默认使用 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="fileAppender"/>
    </root>
</configuration>

2、输出到html格式的日志文件:appender设置为FileAppender,encoder设置为LayoutWrappingEncoder,layout设置为LayoutWrappingEncoder

<configuration>
    <!--name是变量的名称,value是变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量-->
    <property name="html_pattern" value="%d{yyyy-MM-dd HH:mm:ss}%t%-5level%c%M%L%m%n"></property>
    <property name="log_file" value="./logs"></property>

    <!--输出到html格式的日志文件, 这个也属于输出到指定文件, 还是使用FileAppender类-->
    <appender name="htmlFileAppender" class="ch.qos.logback.core.FileAppender">
        <!-- 日志文件保存路径-->
        <file>${log_file}/logback.html</file>
        <!-- html消息格式配置-->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>${html_pattern}</pattern>
            </layout>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="htmlFileAppender"/>
    </root>
</configuration>

注意:我们只能设置打印的日志信息内容,不能设置这个网页的打印格式以及样式。但是,当我们打印出logback.html文件后,我们可以人为的修改其中的样式以及格式这个html中包含 HTML+CSS。在实际的开发中,如果日志文件不是很大,我们可以考虑使用html进行日志打印,因为可读性强。

RollingFileAppender

RollingFileAppender是FileAppender的子类,扩展了FileAppender,具有翻转日志文件的功能。当满足某个特定的条件之后,将日志输出到另外一个文件。

属性名类型描述
fileString被写入文件名,可以是相对目录或绝对目录,如果上级目录不存在会自动创建,无默认值
rollingPolicyRollingPolicy当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
triggeringPolicyTriggeringPolicy告知 RollingFileAppender 合适激活滚动
encoderEncoder参见 ConsoleAppender 的属性
appendboolean如果为 true,日志事件会被追加到文件中,否则的话,清空现存文件。默认为 true
prudentboolean当为true时,不支持FixedWindowRollingPolicy。支持TimeBasedRollingPolicy,但是有两个限制,
1.不支持也不允许文件压缩,2.不能设置file属性,必须留空
1、RollingPolicy滚动策略

RollingPolicy用于配置滚动策略,支持TimeBasedRollingPolicy(基于时间滚动)、SizeAndTimeBasedRollingPolicy(基于大小和时间滚动)、FixedWindowRollingPolicy(固定窗口滚动和大小触发)、其中FixedWindowRollingPolicy和SizeBasedTriggeringPolicy一般同时使用

RollingPolicy滚动策略包括以下几种:

  • TimeBasedRollingPolicy:基于时间的滚动策略
  • SizeAndTimeBasedRollingPolicy:基于文件大小和时间的滚动策略
  • FixedWindowRollingPolicy:基于固定窗口大小的滚动策略,就是将归档日志文件到最大了就写到下一个文件里,而窗口大小就是最多允许日志文件数
  • SizeBasedTriggeringPolicy:FixedWindowRollingPolicy和SizeBasedTriggeringPolicy一般同时使用,用于触发FixedWindowRollingPolicy
2、TimeBasedRollingPolicy

TimeBasedRollingPolicy:它是基于时间来定义轮转策(最常用的轮转策略)例如按天或月。TimeBasedRollingPolicy 既负责轮转的行为,也负责触发轮转。实际上:TimeBasedRollingPolicy 同时实现了 RollingPolicy 与 TriggeringPolicy 接口。

TimeBasedRollingPolicy 的配置需要一个强制的属性 fileNamePattern 以及其它的可选属性:

属性名类型描述
fileNamePatternString该属性的值应该由文件名加上一个 %d的占位符。%d 应包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略日期格式,那么就默认为 yyyy-MM-dd。如果 fileNamePattern 以 .gz 或者 .zip 结尾,将会启动这个压缩特性
maxHistoryint这个可选的属性用来控制最多保留多少数量的归档文件,将会异步删除旧的文件。如单位按月轮转时maxHistory = 6,只会保留6个月的日志。单位根据时间格式维度来区分:按天(yyyy-MM-dd) 按月(yyyy-MM)
totalSizeCapint在有 maxHistory 的限制下,进一步限制所有日志文件大小之和的上限,超过则从最旧的日志开始删除
cleanHistoryOnStartboolean如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为 false。归档文件的删除通常在轮转期间执行。但是,有些应用的存活时间可能等不到轮转触发。对于这种短期应用,可以通过设置该属性为 true,在 appender 启动的时候执行删除操作
<configuration>
    <!--日志输出器:基于时间的滚动策略输出到文件-->
    <appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/logback.log</file>
        <!-- 启用基于时间的滚动策略:TimeBasedRollingPolicy-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 设置轮转策略为:每秒钟轮转 后缀如果设置成.gz或.zip后缀会自动压缩-->
            <fileNamePattern>logs/logback_%d{yyyy-MM-dd_HH-mm-ss}.log</fileNamePattern>
            <!-- 只保留最近1分钟内的日志,超出后会删除旧日志-->
            <maxHistory>60</maxHistory>
            <!-- 所有日志文件大小之和的上限,超出后会删除旧日志-->
            <totalSizeCap>10MB</totalSizeCap>
            <!-- 在appender启动的时候执行删除历史日志操作-->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <!-- 默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%date %-5level -[%thread] %class.%method/%line : %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="rollingFileAppender"/>
    </root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LogbackTest.class);
        // 1秒后启动,每秒跑1次
        new Timer().scheduleAtFixedRate(new TimerTask(){
            @Override
            public void run() {
                logger.error("error test");
                logger.warn("warn test");
                logger.info("info {}", "test");
                logger.debug("debug test ");
                logger.trace("trace test");
            }
        }, 1000, 1000);
    }
}

执行后可以发现1分钟后生成了61个文件。1个logback.log,60个logback_2022-04-03_12-12-xx.log。这是因为每秒生产一个文件,而maxHistory设置了保留最近一分钟的日志(并且每个日志文件都比较小,60个加起来也没超过totalSizeCap总日志大小的上限)。此时还可以发生另外一种情况,就是把totalSizeCap总文件大小设置比较小,才生成几个文件就达到totalSizeCap总日志大小的上限。

3、SizeAndTimeBasedRollingPolicy

SizeAndTimeBasedRollingPolicy:基于时间+大小进行滚动(既能按时轮转,又能限制每个日志文件的大小)是对于第一种的补充。避免单个日志文件过大。实际查看源码也可以看出SizeAndTimeBasedRollingPolicy继承了TimeBasedRollingPolicy,并增加了一个maxFileSize属性。

属性名类型描述
fileNamePatternString该属性的值应该由文件名加上一个 %d的占位符。%d 应包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略日期格式,那么就默认为 yyyy-MM-dd。并且需要包含一个 i% 的占位符,当文件大小超过maxFileSize大小时,则用下标新建文件。如果 fileNamePattern 以 .gz 或者 .zip 结尾,将会启动这个压缩特性
maxFileSizeint按照文件大小进行拆分,当文件大小达到1MB时就会将日志进行压缩
maxHistoryint这个可选的属性用来控制最多保留多少数量的归档文件,将会异步删除旧的文件。如单位按月轮转时maxHistory = 6,只会保留6个月的日志。单位根据时间格式维度来区分:按天(yyyy-MM-dd) 按月(yyyy-MM)
totalSizeCapint在有 maxHistory 的限制下,进一步限制所有日志文件大小之和的上限,超过则从最旧的日志开始删除
cleanHistoryOnStartboolean如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为 false。归档文件的删除通常在轮转期间执行。但是,有些应用的存活时间可能等不到轮转触发。对于这种短期应用,可以通过设置该属性为 true,在 appender 启动的时候执行删除操作
<configuration>
    <!--日志输出器:基于文件大小和时间的滚动策略-->
    <appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 活动中的日志文件名(支持绝对和相对路径) -->
        <file>logs/logback.log</file>
        <!-- 基于时间和文件大小的滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 设置轮转策略为: 每小时轮转, 按照时间和压缩格式来声明文件名-->
            <!-- %i是当文件大小超过maxFileSize大小时, 则用下标新建文件, .gz或.zip后缀会自动压缩-->
            <fileNamePattern>logs/logback_%d{yyyy-MM-dd_HH-mm-ss}_%i.log.zip</fileNamePattern>
            <!-- 每个文件最多1MB, 保留1分钟的历史记录, 但最多200MB-->
            <!-- 单个日志文件的最大大小-->
            <maxFileSize>1MB</maxFileSize>
            <!-- 根据当前轮转策略, 只保留最近1分钟内的日志,超出后会删除旧日志-->
            <maxHistory>60</maxHistory>
            <!-- 所有日志文件大小之和的上限,超出后会删除旧日志-->
            <totalSizeCap>200MB</totalSizeCap>
            <!-- 设置为true并且maxHistory不为零才能起效-->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <!-- 日志输出格式, 默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%date %-5level -[%thread] %class.%method/%line : %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 根记录器 -->
    <root level="INFO">
        <appender-ref ref="rollingFileAppender"/>
    </root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LogbackTest.class);
        // 1秒后启动,每秒跑1次
        new Timer().scheduleAtFixedRate(new TimerTask(){
            @Override
            public void run() {
                logger.error("error test");
                logger.warn("warn test");
                logger.info("info {}", "test");
                logger.debug("debug test ");
                logger.trace("trace test");
            }
        }, 1000, 1000);
    }
}

执行后可以发现会生成文件logback.log与文件logback_2022-04-03_17-42-xx_0.log.zip若干个。若在同一个时间段内生成的单个文件超过了maxFileSize后,%i就会生效(默认是从0开始)

4、FixedWindowRollingPolicy

可以理解成自定义滚动规则,避免使用出现单个文件过大或者日志文件过多的情况。需要同时配置triggeringPolicy用于指定滚动触发规则。对以上的补充。

FixedWindowRollingPolicy 根据固定窗口算法重命名文件,filaNamePattern 表示归档文件的名字。这个属性是必选,并必须包含一个表示整形的占位符 i%

属性名类型描述
fileNamePatternStringFixedWindowRollingPolicy 在重命名日志文件时将会根据这个属性来命名。它必须包含一个 i% 的占位符,该占位符指明了窗口索引的值应该插入的位置。 例如,当该属性的值为 MyLogFile%i.log,最小与最大的值分别为 13。将会产生的归档文件为 MyLogFile1.logMyLogFile2.logMyLogFile3.log。 文件压缩的方式也是通过该属性来指定。例如,设置该属性的值为 MyLogFile%i.log.zip,那么归档文件将会被压缩成 zip 格式。也可以选择压缩成 gz 格式。
minIndexint表示窗口索引的下界
maxIndexint表示窗口索引的上界
<configuration>
    <appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 活动中的日志文件名(支持绝对和相对路径) -->
        <file>logs/logback.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>logs/logback_%i.log.zip</fileNamePattern>
            <minIndex>1</minIndex>
            <maxIndex>5</maxIndex>
        </rollingPolicy>
        <!-- 观察当前活动文件的大小,如果已经大于了指定的值,它会给RollingFileAppender发送信号触发对当前活动文件的轮转-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>10KB</maxFileSize>
        </triggeringPolicy>
        <encoder>
            <pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="rollingFileAppender" />
    </root>
</configuration>

TriggeringPolicy 用于通知何时触发滚动。实现类:SizeBasedTriggeringPolicy

  • SizeBasedTriggeringPolicy:观察当前活动文件的大小,如果大于了指定的值,它会给RollingFileAppender发一个信号触发对当前活动文件的轮转
  • SizeBasedTriggeringPolicy:只接收 maxFileSize 这一个参数,它的默认值是 10MB
  • maxFileSize:可以为字节 / 千字节 /兆字节 /千兆字节,数值 加 KB,MB 或 GB。例:5000000,5000KB,5MB(这3个大小一样) 及 2GB
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LogbackTest.class);
        // 1秒后启动,每秒跑1次
        new Timer().scheduleAtFixedRate(new TimerTask(){
            @Override
            public void run() {
                logger.error("error test");
                logger.warn("warn test");
                logger.info("info {}", "test");
                logger.debug("debug test ");
                logger.trace("trace test");
            }
        }, 1000, 1000);
    }
}

执行代码可以发现:最后生成文件:logback.log、logback_1.log.zip - logback_5.log.zip(zip解压后可以发现文件名:logback_2022-04-03_1754,具体可以参考源码:FixedWindowRollingPolicy)如果fileNamePattern中没有设置以.zip结尾的话,默认名称就是设定名称+Index

5、轮转周期示例 fileNamePattern

适用上面的 SizeAndTimeBasedRollingPolicy 和 TimeBasedRollingPolicy 滚动策略<fileNamePattern></fileNamePattern>

fileNamePattern 格式说明
app_%d.log每天轮转。不指定%d日期格式时默认为 yyyy-MM-dd
app_%d{yyyy-MM}.log每个月开始的时候轮转
app._%d{yyyy-ww}.log每周的第一天(取决于时区)
app_%d{yyyy-MM-dd_HH}.log每小时轮转。如:app_2020-10-24_10.log
app_%d{yyyy-MM-dd_HH-mm}.log每分钟轮转;。如:app_2020-10-24_10-32.log
app_%d{yyyy-MM-dd_HH-mm, UTC}.log每分钟轮转(时间格式是 UTC)
app/%d{yyyy-MM}/%d.log每天轮转。第一个%d被辅助标记。第二个%d为主要标记。如:app/2020-10/2020-10-24.log

重点注意:fileNamePattern中设置的时间格式生产文件名不能使用空格已经冒号等特殊符号(不然不会生产新的滚动文件)

AsyncAppender 异步

上面的Appender日志输出到文件是同步输出的,即每次输出都会直接写IO到磁盘文件。对于高并发,会产生一定的阻塞,造成不必要的性能损耗。Logback提供了日志异步输出的AsyncAppender。处理方式较简单,添加一个基于异步写日志的appender,并指向原配置的appender即可。AsyncAppender 参数说明:

参数默认值说明
discardingThreshold20如果设置discardingThreshold=0,表示 queue 满了,不丢弃,block 线程。默认情况下,当阻塞队列剩余 20% 的容量时,它将丢弃级别跟踪、调试和信息事件,只保留级别警告和错误事件。要保留所有事件,请将 discardingThreshold 设置为0
queueSize256假设 IO 影响 30s,日志和 qps 比例是1:1,单容器压测值 1500 qps 则可以推算出 queueSize 的值,queueSize 的设置公式:30 * 1500=45000
neverBlockfalse如果为 false(默认值),则追加程序将阻止追加到完整队列,而不是丢失消息。设置为 true 时,附加程序只会丢弃消息,不会阻止您的应用程序
includeCallerDatafalse提取呼叫者数据可能相当昂贵。 为了提高性能,默认情况下,当事件添加到事件队列时,不会提取与事件关联的调用者数据。 默认情况下,仅复制线程名称和 MDC 等“廉价”数据。 您可以通过将 includeCallerData 属性设置为 true 来指示此附加程序包含调用方数据。
<configuration>
    <!--输出到文件-->
    <appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
        <file>logs/logback.log</file>
        <!--提高高的日志吞吐量-->
        <immediateFlush>false</immediateFlush>
        <encoder>
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    
    <!--异步输出-->
    <appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 默认如果队列的80%已满,则会丢弃TRACT,DEBUG,INFO级别的日志,若要保留全部日志,设置为0 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能,默认值为256 -->
        <queueSize>1024</queueSize>
        <!-- 设置附加程序只会丢弃消息,不会阻止您的应用程序-->
        <neverBlock>true</neverBlock>
        <!--是否提取调用者数据-->
        <includeCallerData>true</includeCallerData>
        <!-- 添加附加的appender,最多只能添加1个 -->
        <appender-ref ref="fileAppender"/>
    </appender>
    
    <!-- 异步输出关联到root根logger -->
    <root level="INFO">
        <appender-ref ref="asyncAppender" />
    </root>
</configuration>

这里有个需要注意的地方,那就是 AsyncAppender 必须在其引用的 Appender 配置的后面,否则会使配置不生效。

SMTPAppender 邮件

使用SpringBoot + Logback 配置程序异常自动发送邮件:https://mp.weixin.qq.com/s/N4OrKlrtCDeEd95XWGi9YA

现在想更要发生异常自动发送邮件的功能,需要使用到 SMTPAppender 输出日志到邮件组件

1、加入依赖(JavaMail)

<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.7</version>
</dependency>

2、修改logback的xml的配置文件

<configuration debug="true">
    <!--日志输出器:输出到控制台-->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d [%t] [%-5level] %c %M %L %m %n</pattern>
        </encoder>
    </appender>

    <!--邮件设置-->
    <property name="smtpHost" value="smtp.qq.com"/>
    <!--QQ邮箱的SMTP(SLL)端口为465或587,其他邮箱自行去查看)-->
    <property name="smtpPort" value="587"/>
    <property name="mailFrom" value="xxx@qq.com"/>
    <property name="username" value="xxx@qq.com"/>
    <property name="mailTo" value="xxx@qq.com,xxx@qq.com"/>
    <property name="password" value="fxomimpmgpjgbhxx"/>
    <property name="SSL" value="false"/>
    <property name="mailSubject" value="Exception information reminder"/>
    <appender name="mailAppender" class="ch.qos.logback.classic.net.SMTPAppender">
        <smtpHost>${smtpHost}</smtpHost>
        <smtpPort>${smtpPort}</smtpPort>
        <username>${username}</username>
        <password>${password}</password>
        <SSL>${SSL}</SSL>
        <asynchronousSending>false</asynchronousSending>
        <to>${mailTo}</to>
        <from>${mailFrom}</from>
        <subject>${mailSubject}</subject>
        <layout class="ch.qos.logback.classic.html.HTMLLayout">
            <!-- 这里配置的是邮件表格的形式 跟截图的邮件格式是一样 其实不加这一块也是样式也是一样的  但是可以自己研究自定义样式-->
            <Pattern>%date%level%thread%logger{0}%line%message</Pattern>
        </layout>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <root level="INFO">
        <appender-ref ref="consoleAppender"/>
        <appender-ref ref="mailAppender"/>
    </root>
</configuration>

3、测试代码,输出ERROR级别日志,会自动发送到邮件中

package com.xyz;

import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = (Logger)LoggerFactory.getLogger(LogbackTest.class);

        String s = null;
        try {
            s.equals("");
        } catch (Exception e) {
            logger.error("error message: {}",e);
        }
    }
}

4、邮件中收到内容如下:

DateLevelThreadLoggerLineOfCallerMessage
2022-04-06 11:37:18,549ERRORmainLogbackTest14error message: {}
java.lang.NullPointerException: null
at com.xyz.LogbackTest.main(LogbackTest.java:14)
Logback + ELK 配置

GitHub地址:https://github.com/logfellow/logstash-logback-encoder/blob/main/README.md

如果项目中的日志采用的是基于ELK(Elasticsearch、Logstash、Kibana)来进行日志管理。则可以在pom文件中引入logstash-logback-encoder依赖

<!--由于配置中使用了json格式的日志输出,所以需要引入如下依赖-->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>5.1</version>
</dependency>

然后在logback-spring.xml中配置对应的appender:

<!--logstash 配置部分-->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    <!-- 发送到Logstash提供的服务地址 -->
    <destination>192.168.0.11:5061</destination>
    <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
        <customFields>{"project": "springboot-logback-elk"}</customFields>
    </encoder>
</appender>

该appender的使用与其他appender的使用无异。主要使用了LogstashTcpSocketAppender类来完成与Logstash的通信。其中destination为Logstash提供的服务地址。customFields为自定义的参数,便于Logstash识别日志是从哪个业务系统传输过来的。

如下是参考一个详细(完整的logback配置示例ELK整合包含生成json日志:https://www.zhangshengrong.com/p/OgN5Dvvxan/)

<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/logback.json</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/logback-%d{yyyy-MM-dd}.json</fileNamePattern>
        <maxHistory>7</maxHistory>
    </rollingPolicy>
    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
        <providers>
            <timestamp>
                <timeZone>UTC</timeZone>
            </timestamp>
            <pattern>
                <pattern>
                    {
                    "ip": "${ip}",
                    "app": "${appName}",
                    "level": "%level",
                    "trace": "%X{X-B3-TraceId:-}",
                    "span": "%X{X-B3-SpanId:-}",
                    "parent": "%X{X-B3-ParentSpanId:-}",
                    "thread": "%thread",
                    "class": "%logger{40}",
                    "message": "%message",
                    "stack_trace": "%exception{10}"
                    }
                </pattern>
            </pattern>
        </providers>
    </encoder>
</appender>

07、encoder & pattern元素

官网地址:

  • https://logback.qos.ch/manual/encoders.html
  • https://logback.qos.ch/manual/layouts.html
  • https://logback.qos.ch/manual/layouts.html#conversionWord

encoder和layout一样,用来格式化输出。encoder是在logback 0.9.19 版本之后引入的。在这个版本之前,大部分的appender使用layout来格式化输出,但之后FileAppender和他的子类推荐使用encoder来代替layout。常用的是PatternLayoutEncoder(这是默认的encoder)所以我们不用指定,直接使用encoder标签就行。PatternLayoutEncoder的组成和layout一样。如下:

Pattern表达式

Conversation WordEffect
d{pattern}、date{pattern}日志打印的时间,语法与java.text.SimpleDateFormat兼容。Conversion Pattern Result
%d2006-10-20 14:06:49,812
%date2006-10-20 14:06:49,812
%date{ISO8601}2006-10-20 14:06:49,812
%date{HH:mm:ss.SSS}14:06:49.812
%date{dd MMM yyyy ;HH:mm:ss.SSS}20 oct. 2006;14:06:49.812
%d{dd MMM yyyy ;HH:mm:ss.SSS,Australia/Perth}05 4月 2022 ;11:34:13.168
Conversation WordEffect
变量名%date 时间打印详情请参考上方
%date{pattern, timezone}、%d日志打印的时间。pattern与java.text.SimpleDateFormat兼容
pattern不填写默认值是:yyyy-MM-dd hh:mm:ss,SSS。
第二个参数是时区,例如:%date{HH:mm:ss.SSS, Australia/Perth}
%logger、%lo、%c{length}输出当前日志名称。没有length则输出全名。如:com.xyz.LogbackTest
%class、%C{length}输出日志调用所在类,没有length则打印全类名。如:com.xyz.LogbackTest(与%c一样)
%method、%M输出日志所在方法(没有length参数)如:main
%caller{length}日志调用位置,length代表日志深度,如:Caller+0 at com.xyz.LogbackTest.main(LogbackTest.java:9)
%thread、%t输出线程名(没有length参数)
%level、%le、%p输出日志级别(没有length参数)
%message、%msg、%m输出日志记录内容,就是调用logger的方法的时候传入的log字符串
%exception、%ex输出异常信息。如:logger.error("error...",new Exception("exception"));
%line、%L输出日志的行数。如:9
%file、%F输出调用logger的java源码文件名,速度不快,避免使用。如:LogbackTest.java
%n换行符
%输出%号
特殊占位符
%X{user}表示可以获取外部自定义传入的值, 如:%X{user}=》org.slf4j.MDC.put("user", "xx-yy-zz");
宽度设置可以限制上面的ConversationWord的宽度和左右对齐
%20logger最小宽度20,当小于20时,则左侧留空白。右对齐
%-20logger最小宽度20,当小于20时,则右侧留空白。左对齐
%.30logger最大宽度30,超出时从头部开始截断。如:%.2、test=》st
%.-30logger最大宽度30,超出时从末尾开始截断。如:%.-2、test=》te
%20.30logger最小20,最大30,小于20的时候右对齐,大于30的时候开始处截断
%-20.30logger最小20,最大30,小于20的时候左对齐,大于30的时候开始处截断
显示设置
%highlight()突出显示,如:%highlight(%-5level)
%green(%red、%blue、%white)字体显示为指定颜色
{length}可指定长度,如:%logger{36}
网络访问设置需要依赖logger-access模块
%remoteIP、%a远程IP
%localIP、%A本地IP
%clientHost、%h远程主机名
%localPort本地端口
%requestMethod、%mhttp请求方法
%protocol、%Hhttp请求协议
%statusCode、%shttp请求status code
%requestURL、%rhttp请求地址
%requestURI、%Uhttp请求资源地址
%queryString、%qhttp请求参数
%server、%v服务器地址
%elapsedTime、%Dhttp请求处理的时间,单位是毫秒
%elapsedSeconds、%Thttp请求处理的时间,单位是秒
%date、%t日志记录时间
%threadName、%I处理请求的线程名
%reqAttribute{attributeName}http请求attribute值
%reqCookie{cookie}http请求cookie值
%reqContenthttp请求体内容
%fullRequesthttp完整请求
%responseContenthttp响应
%fullResponsehttp完整响应

格式化修饰器:可以限制上面的ConversationWord的宽度和左右对齐。比如:

  • 左对齐修饰符(-):接着是可选的最小宽度修饰符,用十进制数表示。如果字符小于最小宽度,则左填充或右填充,默认是左填充(即右对齐),填充符为空格。如果字符大于最小宽度,字符永远不会被截断
  • 最大宽度修饰符(.):后面加十进制数。如果字符大于最大宽度,则从前面截断。后面加减号“-”在加数字,表示从尾部截断

1、用来指定日志的输出格式及编码等其他配置(encoder元素)

<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
    <encoder><!--日志文件输出格式, 全称形式 及 简写形式-->
        <!--2022-04-05 11:17:26,325 com.xyz.LogbackTest com.xyz.LogbackTest main INFO main INFO com.xyz.LogbackTest : test pattern...-->
        <pattern>%d %lo %C %M %le %t %le %lo : %m%n</pattern>
        <!--2022-04-05 11:18:00,832 com.xyz.LogbackTest com.xyz.LogbackTest main INFO main : test pattern...-->
        <pattern>%date %logger %class %method %highlight(%level) %thread : %msg%n</pattern>
        <!--2022-04-05 11:18:42.394 default [main] INFO  com.xyz.LogbackTest - test pattern...-->
        <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        <!-- 设置字符编码 -->
        <charset class="java.nio.charset.Charset">UTF-8</charset>
        <!-- 将 immediateFlush 设置为 false 可以获得更高的日志吞吐量 -->
        <immediateFlush>false</immediateFlush>
    </encoder>
</appender>

2、使用特殊占位符%X{}示例:

<configuration debug="false">
    <!--日志输出器:输出到控制台-->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d [%X{uuid}] [%X{x}] [%t] [%-5level] %c %M %L %m %n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="consoleAppender"/>
    </root>
</configuration>
package com.xyz;
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = (Logger)LoggerFactory.getLogger(LogbackTest.class);
        org.slf4j.MDC.put("uuid", UUID.randomUUID().toString());
        logger.info("info {}", "test");
    }
}
2022-04-06 18:46:33,795 [4effd2ef-a4de-4a6c-82c1-c1d522e9aa76] [] [main] [INFO ] com.xyz.LogbackTest main 10 info test 

08、filters 过滤器元素

官网地址:https://logback.qos.ch/manual/filters.html

Filter是日志过滤器,是appender里面的子元素。执行一个过滤器会有返回DENY、NEUTRAL、ACCEPT三个枚举值中的一个。appender 有多个过滤器时,按照配置顺序执行。

  • DENY:日志将立即被抛弃不再经过其他过滤器
  • NEUTRAL:有序列表里的下个过滤器过接着处理日志
  • ACCEPT:日志会被立即处理,不再经过剩余过滤器
1、LevelFilter

LevelFilter 基于级别来过滤日志事件。如果事件的级别与配置的级别相等,过滤器会根据配置的 onMatch 与 onMismatch 属性,接受或者拒绝事件

<configuration>
    <!--日志输出器:输出到控制台,只打印INFO级别的日志-->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 日志级别(针对具体指定级别)过滤器-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 只显示INFO级别日志-->
            <level>INFO</level>
            <!-- INFO级别的日志, 被立即执行不再经过其他过滤器-->
            <onMatch>ACCEPT</onMatch>
            <!-- 不是INFO级别的日志, 立即被抛弃不再经过其他过滤器-->
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="consoleAppender" />
    </root>
</configuration>
2、ThresholdFilter

ThresholdFilter 基于给定的临界值来过滤事件。如果事件的级别等于或高于给定的临界值,当调用 decide() 时,ThresholdFilter 将会返回 NEUTRAL。但是事件的级别低于临界值将会被拒绝。简单来说:针对指定级别及以上级别进行日志过滤

<configuration>
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 日志级别(针对指定级别及以上级别)过滤器-->
        <!-- 会打印INFO及比INFO等级高的日志-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="consoleAppender" />
    </root>
</configuration>
3、EvaluatorFilter

EvaluatorFilter: 求值过滤器,评估、鉴别日志是否符合指定条件。有一下子节点:

  • <evaluator>:鉴别器,常用的鉴别器是JaninoEventEvaluato,也是默认的鉴别器,它以任意的java布尔值表达式作为求值条件,求值条件在配置文件解释过成功被动态编译,布尔值表达式返回true就表示符合过滤条件。evaluator有个子标签<expression>,用于配置求值条件
  • <onMatch>:用于配置符合过滤条件的操作
  • <onMismatch>:用于配置不符合过滤条件的操作

求值表达式作用于当前日志,logback向求值表达式暴露日志的各种字段(evaluator标签中可以用的对象和值):

NameTypeDescription
eventLoggingEvent与记录请求相关联的原始记录事件,下面所有变量都来自event,例如,event.getMessage()返回下面"message"相同的字符串
messageString日志的原始消息,例如,设有logger mylogger,“name"的值是"AUB”,对于 mylogger.info(“Hello {}”,name); "Hello {}"就是原始消息。
formattedMessageString日志被各式话的消息,例如,设有logger mylogger,“name"的值是"AUB”,对于 mylogger.info(“Hello {}”,name); "Hello Aub"就是格式化后的消息。
loggerStringlogger 名。
loggerContextLoggerContextVO日志所属的logger上下文。
levelint级别对应的整数值,所以 level > INFO 是正确的表达式。
timeStamplong创建日志的时间戳。
markerMarker与日志请求相关联的Marker对象,注意“Marker”有可能为null,所以你要确保它不能是null。
mdcMap包含创建日志期间的MDC所有值得map。访问方法是:mdc.get(“myKey”) 。mdc.get()返回的是Object不是String,要想调用String的方法就要强转,例如,((String) mdc.get(“k”)).contains(“val”) .MDC可能为null,调用时注意。
throwablejava.lang.Throwable如果没有异常与日志关联"throwable" 变量为 null. 不幸的是, “throwable” 不能被序列化。在远程系统上永远为null,对于与位置无关的表达式请使用下面的变量throwableProxy
throwableProxyIThrowableProxy与日志事件关联的异常代理。如果没有异常与日志事件关联,则变量"throwableProxy" 为 null. 当异常被关联到日志事件时,“throwableProxy” 在远程系统上不会为null

过滤器还可以来自自带的或表达式直接写在配置文件中。

<!-- Janino 是一个极小、极快的 开源Java 编译器-->
<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version>3.1.6</version>
</dependency>
<configuration>
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 根据判断条件的过虑器-->
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator>
                <!--expression为java代码,需要添加groupId为org.codehaus.janino,artifactId为janino的maven包-->
                <expression><!-- 默认为 ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
                    // 根据日志消息判断
                    if(event.getMessage().contains("url")){
                    return true;
                    }
                    if(formattedMessage.contains("url")){
                    return true;
                    }
                    return false;
                </expression>
            </evaluator>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="ALL">
        <appender-ref ref="consoleAppender"/>
    </root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LogbackTest.class);
        // 1秒后启动,每秒跑一次
        new Timer().scheduleAtFixedRate(new TimerTask(){
            @Override
            public void run() {
                logger.error("error test");
                logger.warn("warn test");
                logger.info("info {}", "url");
                logger.debug("debug test ");
                logger.trace("trace url");
            }
        }, 1000, 1000);
    }
}
2022-04-03 22:46:31,251 1421 [Timer-0] INFO  LogbackTest - info url
2022-04-03 22:46:31,251 1421 [Timer-0] TRACE LogbackTest - trace url
2022-04-03 22:46:32,254 2424 [Timer-0] INFO  LogbackTest - info url
2022-04-03 22:46:32,254 2424 [Timer-0] TRACE LogbackTest - trace url

EvaluatorFilter 中还有一个特殊的标签,基本很少用,了解即可。

  • <matcher> :匹配器,尽管可以使用String类的matches()方法进行模式匹配,但会导致每次调用过滤器时都会创建一个新的Pattern对象,为了消除这种开销,可以预定义一个或多个matcher对象,定以后就可以在求值表达式中重复引用。<matcher><evaluator>的子标签。
  • <matcher>中包含两个子标签,一个<name>,用于定义matcher的名字,求值表达式中使用这个名字来引用matcher;另一个<regex>,用于配置匹配条件
<configuration debug="true">   
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">   
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">   
            <evaluator>           
                <matcher>   
                    <Name>odd</Name>   
                    <!-- filter out odd numbered statements -->   
                    <regex>statement [13579]</regex>   
                </matcher>   

                <expression>odd.matches(formattedMessage)</expression>   
            </evaluator>   
            <OnMismatch>NEUTRAL</OnMismatch>   
            <OnMatch>DENY</OnMatch>   
        </filter>   
        <encoder>   
            <pattern>%-4relative [%thread] %-5level %logger - %msg%n</pattern>   
        </encoder>   
    </appender>   

    <root level="DEBUG">   
        <appender-ref ref="STDOUT" />   
    </root>   
</configuration>  
4、自定义过滤器

通过实现ch.qos.logback.core.filter.Filter接口可以自定义过滤器,自定义过滤器类LogbackUrlFilter.class,示例如下:

package com.xyz;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

public class LogbackUrlFilter extends Filter<ILoggingEvent> {
    @Override
    public FilterReply decide(ILoggingEvent event) {
        if(event.getMessage().contains("url")){
            return FilterReply.ACCEPT;
        }
        if(event.getFormattedMessage().contains("url")){
            return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
    }
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(LogbackTest.class);
        // 1秒后启动,每秒跑一次
        new Timer().scheduleAtFixedRate(new TimerTask(){
            @Override
            public void run() {
                logger.error("error test");
                logger.warn("warn test");
                logger.info("info {}", "url");
                logger.debug("debug test ");
                logger.trace("trace url");
            }
        }, 1000, 1000);
    }
}
2022-04-03 22:46:31,251 1421 [Timer-0] INFO  LogbackTest - info url
2022-04-03 22:46:31,251 1421 [Timer-0] TRACE LogbackTest - trace url
2022-04-03 22:46:32,254 2424 [Timer-0] INFO  LogbackTest - info url
2022-04-03 22:46:32,254 2424 [Timer-0] TRACE LogbackTest - trace url

通常情况下,日志输出会配置三个,一个控制台输出用于开发阶段;一个INFO及以上级别的日志输出,可追踪相应的生产日志;一个单独ERROR级别的日志输出,方便快速检查出异常日志。都可以通过Appender和Filter来控制。

09、logger 元素

logger用来设置某一个类或者某个包的日志输出级别、以及关联的appender,这样就可以控制非业务日志不写到对应的文件日志中。

logger标签中包含三个属性:

  • name:要输出日志的包名或者类名,比如com.xyz。必选项

  • level:设置日志的级别(不区分大小写:TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF)如果未设置,则logger会向上继承最近一个非空级别。可选项

  • additivity:是否将日志向上级传递,默认为 true。可选项

logger通过1个或多个子节点appender-ref来控制日志的输出:

<!--表示org.springframework包下的日志以warn级别输出-->
<logger name="org.springframework" level="WARN"/>

<!--表示com包下的日志以debug级别输出-->
<logger name="com" level="debug" />

<!--对指定包指定appender进行日志控制,由于设置了info级别,additivity为true,而且关联consoleAppender,因此info以上级别的日志会输出到控制台-->
<!--同时会把日志上传到父级,即root。若root也有配置consoleAppender的输出的话,会在控制台输出两次。additivity为false,则不会。-->
<!-- 只打印包com.xyz下的日志,最低级别从info开始-->
<logger name="com.xyz" level="info" additivity="true">
    <appender-ref ref="consoleAppender" />
</logger>

10、root 元素

root元素配置根记录器。它是个特殊的logger,是所有logger的根节点,只能指定日志级别及level(默认DEBUG)和appender-ref(输出的appender)

<!--这是第一种打印到多个位置的配置(此时打印到控制台和文件的日志级别都是info级别)-->
<root level="info">
    <!--引入appender,日志记录器,使用name属性来获取指定的appender对象-->
    <appender-ref ref="consoleAppender"/>
    <appender-ref ref="fileAppender"/>
</root>
<!--这是第二种打印到多个位置的配置(此时虽然代码中写的控制台和文件的打印级别不同,但是此时打印出来的都是info级别,以最后一个级别为准)-->
<root level="ALL">
    <appender-ref ref="fileAppender"/>
</root>
<!--最后一个root会覆盖前面所有的root,只会生效一个root配置-->
<root level="info">
    <appender-ref ref="consoleAppender"/>
</root>

level属性的值可以是不区分大小写的字符串TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一。root元素可以包含零个或多个appender-ref元素;被引用的每个appender都被添加到根记录器中。

root元素和logger元素的区别?

root是根logger,是一种logger,root和根logger其实是一回事;只不过root中不能有 name 和 additivity 这两个属性

11、include 包含元素

文件包含元素:将配置文件的一部分包含在另一个文件中,可以通过标签include来引入另一个配置文件。如下所示:

<configuration>
    <include file="src/main/java/resources/includedConfig.xml"/>
    <root level="DEBUG">
        <appender-ref ref="includedConsole" />
    </root>
</configuration>

includedConfig.xml文件定义了被引用的内容:

<included>
    <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>"%d - %m%n"</pattern>
        </encoder>
    </appender>
</included>

include标签引入的文件可以为一个文件,一个类路径上的资源,或者一个URL。如下:

<include file="src/main/java/resources/includedConfig.xml"/>
<include resource="includedConfig.xml"/>
<include url="http://xxx.com/includedConfig.xml"/>

如果被引用的文件不存在,logback会打印内部的状态信息。如果包含的文件是可选的,可以通过optional属性设置为true来进制打印显示警告信息。

<include optional="true"/>

12、springProfile 元素

如果是基于SpringBoot项目,针对不同环境(profile)有不同的日志输出,比如开发(dev)环境只输出到控制台,生产环境(prod)只输入INFO和ERROR,那则可用到Spring支持的profile机制。对应的元素为springProfile。profile的值与springboot中配置文件的spring.profiles.active值进行对照。

<configuration>
    <springProfile name="dev">
        <!-- configuration to be enabled when the "dev" profile is active -->
        <logger name="com.example.demo.controller" level="DEBUG" additivity="false">
            <appender-ref ref="consoleLog"/>
        </logger>
    </springProfile>

    <springProfile name="dev | test">
        <!-- configuration to be enabled when the "dev" or "test" profiles are active -->
        <logger name="com.example.demo.controller" level="INFO" additivity="false">
            <appender-ref ref="consoleLog"/>
        </logger>
    </springProfile>

    <springProfile name="!production">
        <!-- configuration to be enabled when the "production" profile is not active -->
        <logger name="com.example.demo.controller" level="INFO" additivity="false">
            <appender-ref ref="consoleLog"/>
        </logger>
    </springProfile>
</configuration>

4、Logback其他操作

1、动态切换日志级别

Logback提供有代码方式直接修改日志级别,我们可以获取LoggerContext对象修改日志级别及其他配置。如下是Java代码实现(无配置文件)

package com.xyz;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;

public class LogbackTest {
    public static void main(String[] args) {
        Logger logger = (Logger)LoggerFactory.getLogger(LogbackTest.class);

        System.out.println("============使用默认的日志输出级别DEBUG==============");
        printLogs(logger);

        System.out.println("============动态设置日志输出级别为ERROR==============");
        // 返回正在使用的 ILoggerFactory 实例.
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        // 根据包路径或类路径获取日志记录器,"ROOT" 表示根记录器: Logger getLogger(final String name);
        loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.toLevel("error"));
        printLogs(logger);

        System.out.println("============动态设置日志输出级别为WARN==============");
        loggerContext.getLogger("com.xyz").setLevel(Level.valueOf("warn"));
        printLogs(logger);
    }

    private static void printLogs(Logger logger) {
        logger.error("test error...");
        logger.warn("test warn...");
        logger.info("test info...");
        logger.debug("test debug...");
        logger.trace("test trace...");
    }
}
============使用默认的日志输出级别DEBUG==============
18:16:51.570 [main] ERROR com.xyz.LogbackTest - test error...
18:16:51.570 [main] WARN com.xyz.LogbackTest - test warn...
18:16:51.570 [main] INFO com.xyz.LogbackTest - test info...
18:16:51.570 [main] DEBUG com.xyz.LogbackTest - test debug...
============动态设置日志输出级别为ERROR==============
18:16:51.570 [main] ERROR com.xyz.LogbackTest - test error...
============动态设置日志输出级别为WARN==============
18:16:51.570 [main] ERROR com.xyz.LogbackTest - test error...
18:16:51.570 [main] WARN com.xyz.LogbackTest - test warn...

2、停止logback-classic

为了释放 logback-classic 资源,停止 logback context 是一个好主意。如果停止,会关闭所有 loggers 关联的 appenders,并有序的停止所有活动线程。

import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;

// 返回正在使用的 ILoggerFactory 实例.
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop(); // stop logback-classic

3、模块logback-access

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

  1. 将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下(可以从本地的maven库直接copy)

    <!--logback的基础模块,实际上也可以不导入,因为logback-access中也包含了基础模块包-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
    <!--实际可以只导入logback-access包即可-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>1.2.3</version>
    </dependency>
    
  2. 修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:

    <Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
    
  3. logback默认会在$TOMCAT_HOME/conf下查找文件:logback-access.xml

    <?xml version="1.0" encoding="UTF-8"?> 
    <configuration> 
        <!-- always a good activate OnConsoleStatusListener --> 
        <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/> 
        <property name="LOG_DIR" value="${catalina.base}/logs"/> 
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 
            <file>${LOG_DIR}/access.log</file> 
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 
                <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern> 
            </rollingPolicy>
            <encoder> 
                <!-- 访问日志的格式,官网有固定的枚举值 对应的表达式类型,两种写法都可以--> 
                <!--<pattern>%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"</pattern>-->
                <pattern>combined</pattern> 
            </encoder> </appender> 
        <appender-ref ref="FILE"/> 
    </configuration>
    
  4. 结果验证:启动tomcat,刷新页面,查看日志:logs/access.log

    127.0.0.1 - - [06/四月/2022:10:43:40 +0800] "GET / HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55"
    127.0.0.1 - - [06/四月/2022:10:43:40 +0800] "GET / HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55"
    
  5. 官方配置:https://logback.qos.ch/access.html#configuration

5、MDC分布式应用追踪请求

此方式可以用在任何日志实现框架中使用,如:Logback、log4j2 等

上面所讲的日志都是在单个应用系统下记录日志的。一旦进入分布式系统,很可能就会出现日志错乱,对日志追踪和排查造成难题。如果使用像ELK这类框架将日志进行归集统一处理,也需要一个标识,来记录日志的整个请求处理过程。

Slf4j提供了MDC(Mapped Diagnostic Contexts诊断上下文映射),可以让开发人员在诊断上下文中放置信息。通过ThreadLocal实现了线程与线程之间的数据隔离。在输出时可以通过标识符%X{key}来输出MDC中设置的内容。【此方式可以用在任何日志实现框架中使用,如:Logback、log4j2 …】

分布式应用追踪请求实现思路如下:

Web拦截器增加唯一ID »» 增加ID到MDC中 »» 调用其他服务时ID作为Header参数 »» 输出日志添加ID »» 请求结束,清除ID »» 根据相同ID排查日志

下面来看一下具体的实现代码。这里使用的是SpringBoot + Filter,也可以使用HandlerInterceptor拦截器(效果一样)

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
import org.slf4j.MDC;

public class CorrelationIdLoggingFilter implements Filter {
    private static final String CORRELATION_ID = "correlation_id";
    private static final String UU_ID = "uu_id";

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;

        String correlationId = req.getHeader(CORRELATION_ID);
        MDC.put(CORRELATION_ID, correlationId);
        String uuId = UUID.randomUUID().toString();
        MDC.put(UU_ID, uuId);

        chain.doFilter(request, response);
        MDC.remove(CORRELATION_ID);
        MDC.remove(UU_ID);
    }
}
@Configuration
public class FilterRegistration {
    @Bean
    public FilterRegistrationBean correlationIdLoggingFilterRegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CorrelationIdLoggingFilter()); // 可以new也可以Bean注入
        registration.setName("correlationIdLoggingFilter"); // 设置过滤器名称
        registration.addUrlPatterns("/*"); // 拦截路径
        registration.setOrder(-1); // 设置执行顺序,数字越低优先级越高
        return registration;
    }
}
<!--如下是Logback.xml配置文件配置-->
<property name="log_pattern" value="%d{MM-dd HH:mm:ss.SSS} [%X{uu_id}] [%X{correlational_id}] [%5p] [%40.40c{1.}:%3L] - %m%n"/>
<!--如下是SpringBoot全局配置文件配置-->
logging.pattern.console=%d{MM-dd HH:mm:ss.SSS} [%X{uu_id}] [%X{correlation_id}] [%5p] [%40.40c{1.}:%3L] - %m%n

启动后测试可以看出:启动时候没有correlation_id与uu_id值,当有请求进来才会生成:

curl -H 'Content-Type:application/json' -H 'correlation_id:xxxxx-yyyyy-zzzzz'  http://localhost:8080/logback/logs
04-15 12:16:29.740 [] [] [ INFO] [             o.s.b.w.e.t.TomcatWebServer:220] - Tomcat started on port(s): 8080 (http) with context path ''
04-15 12:16:29.750 [] [] [ INFO] [                      c.x.XyzApplication: 61] - Started XyzApplication in 1.504 seconds (JVM running for 2.672)
04-15 12:16:51.659 [] [] [ INFO] [                       o.a.c.c.C.[.[.[/]:173] - Initializing Spring DispatcherServlet 'dispatcherServlet'
04-15 12:16:51.659 [] [] [ INFO] [               o.s.w.s.DispatcherServlet:525] - Initializing Servlet 'dispatcherServlet'
04-15 12:16:51.660 [] [] [ INFO] [               o.s.w.s.DispatcherServlet:547] - Completed initialization in 1 ms
04-15 12:16:51.678 [e91e1660-69d2-4fd8-bdcb-304c0cd89c70] [xxxxx-yyyyy-zzzzz] [ERROR] [                    c.x.Log4j2Controller: 24] - hello, I am error!
04-15 12:16:51.678 [e91e1660-69d2-4fd8-bdcb-304c0cd89c70] [xxxxx-yyyyy-zzzzz] [ WARN] [                    c.x.Log4j2Controller: 25] - hello, I am warn!
04-15 12:16:51.680 [e91e1660-69d2-4fd8-bdcb-304c0cd89c70] [xxxxx-yyyyy-zzzzz] [ INFO] [                    c.x.Log4j2Controller: 26] - hello, I am info!

6、SpringBoot集成Logback

SpringBoot官网文档:https://docs.spring.io/spring-boot/docs/2.5.0/reference/html/features.html#features.logging

1、SpringBoot日志依赖关系

  1. spring-boot-starter是SpringBoot启动器,每一个SpringBoot应用都会依赖到它

  2. sping-boot-starter依赖Sping-boot-starter-looging

  3. SpringBoot 底层默认使用 SLF4J+ Logback 方式进行日志记录

  4. SpringBoot 使用中间替换包把其的日志框架都替换成了SLF4J

  5. SpringBoot 能自动适配所有的日志框架,且底层使用SLF4J + Logback方式记录日志,引入其他框架时,只需要把这个框架依赖的日志框架排除掉即可

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- 去掉springboot默认配置 -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- 引入log4j2依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    

2、SpringBoot全局日志设置

SpringBoot官网配置文件详解:https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#application-properties.core

  1. 日志的级别由低到高分别为:trace(跟踪) < debug(调试) < info(信息) < warn(警告) < error(错误)

  2. 在配置文件中调整输出的日志级别,日志就只会在这个级别及以后的高级别生效,SpringBoot 默认使用 INFO 级别(Logback 默认是 DEBUG)

  3. SpringBoot 的全局配置文件 “application.properties” 或 “application.yml” 中可以修改日志配置项:

    # 默认名logback-spring.xml,如果要设置其他名称则需要如下配置
    # logging.config=classpath:logback-spring.xml
    # 设置根节点的日志级别输出,root表示整个项目
    logging.level.root=INFO
    # 指定特定包及类的日志输出级别,未指定就按root设置的级别输出,如果root也未指定则按SpringBoot的默认级别info输出
    logging.level.org.springframework.web=DEBUG
    logging.level.org.hibernate=ERROR
    logging.level.com.xyz=DEBUG
    
    # 配置日志输出的文件,这两个选一个配置就可以了,一起配置的话,name的生效.每次启动都是追加日志
    # .name=具体文件, 写入指定的日志文件.文件名可以是确切的位置,也可以是相对于当前目录的位置(相对路径为与pom.xml同级)
    # .path=具体目录, 写入spring.log文件到指定目录.目录名可以是确切的位置,也可以是相对于位置(文件名spring.log无法更改)
    logging.file.name=logs/spring-boot.log
    logging.file.path=logs/
    
    # 指定控制台输出的日志格式,例如: %d{yyyy-MM-dd HH:mm:ss} [%X{uu_id}] -- [%thread] %-5level %logger{50} %msg%n
    # 1、%d 表示日期时间,
    # 2、%thread 表示线程名,
    # 3、%‐5level 级别从左显示5个字符宽度
    # 4、%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
    # 5、%msg 日志消息,
    # 6、%n 换行符
    # 7、%line 显示日志输出位置的行号,方便寻找位置
    # 8、%X 特殊占位符,%X{uu_id}是获取uu_id的值,代码中设置uu_id值:org.slf4j.MDC.put("uuid", "xx-yy-zz");
    # 配置日志输出格式, .file是配置输出到文件的日志格式, .console是配置输出到控制台的日志格式
    logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{50}:%line %msg%n
    logging.pattern.file=%d{yyyy-MM-dd HH:mm} -- [%thread] %-5level %logger{50} %msg%n
    
    # 设置日志记录器组,将相关的记录器组合在一起,然后设置日志记录器组的日志级别为TRACE
    logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
    logging.level.tomcat=TRACE
    # SpringBoot包括以下预定义的日志记录组,可以开箱即用
    # web: org.springframework.core.codec, org.springframework.http, org.springframework.web
    # sql: org.springframework.jdbc.core, org.hibernate.SQL
    logging.level.web=INFO
    logging.level.sql=DEBUG
    
  4. SpringBoot全局配置中的日志配置优先级高于第三方日志框架配置文件(application.properties > logback-spring.xml)

3、SpringBoot官网文档翻译

1、SpringBoot日志生成路径
logging.file.namelogging.file.path示例说明
(none)(none)只在控制台输出
指定文件名(none)demo.log输出到当前项目根路径下的 demo.log 文件中
(none)指定目录logs/log_lzy输出到当前项目所在磁盘根路径下的/logs/log_lzy目录中的 spring.log 文件中
指定文件名指定目录当两个同时指定时, name会生效。推荐使用logging.file.name设置,因为它可自定义文件名
# 配置日志输出的文件,这两个选一个配置就可以了,一起配置的话,name的生效. 每次启动都是追加日志
# .name=具体文件, 写入指定的日志文件.文件名可以是确切的位置,也可以是相对于当前目录的位置(相对路径为与pom.xml同级)
# .path=具体目录, 写入spring.log文件到指定目录.目录名可以是确切的位置,也可以是相对于位置(文件名spring.log无法更改)
logging.file.name=logs/spring-boot.log
logging.file.path=logs/
  1. 默认情况:日志文件超过10M时,会新建文件进行递增,如logback.log、logback1.log、logback2.log,使用logging.file.max-size更改大小限制
  2. 默认情况:日志只记录到控制台,不写入日志文件,如果要在控制台输出之外写入到日志文件,则需要设置 .file 或 .path 属性
  3. 默认情况,logging.file.* 等配置使用的都是RollingFileAppender

日志输出到文件是非常有用的,比如命令行启动一个 jar 包,但是它一执行就自动报错,然后退出了,还来不急看清错误信息,此时可以修改配置把日志输出到指定文件中,再次启动错误信息就会自动存放进去

2、SpringBoot日志级别设置

SpringBoot Log Levels 日志级别官方文档:https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#features.logging.log-levels

  1. 通过配置logging.level.<logger name>=<level>可以设置所有受支持的日志系统的日志级别,其中 level 是 ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF 之一

  2. 可以使用 logging.level.root 配置根日志记录器:

    # 未特别指定的仍然按 Spring Boot 的默认的 logging.level.root 设置输出.
    logging.level.root=WARN  # 根节点日志级别,即整个应用的日志级别设置,默认为 info
    logging.level.org.springframework.web=DEBUG   # 单独某个包的日志级别设置,spring web 包日志输出级别
    logging.level.org.hibernate=ERROR    # 单独某个包的日志级别设置,hibernate 日志输出级别
    logging.level.com.xzy=info  # 自己项目中指定包下的日志输出级别
    
3、SpringBoot日志记录器组

SpringBoot Log Groups 日志记录器组官方文档:https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#features.logging.log-groups

  1. 将相关的记录器组合在一起,以便可以同时对它们进行配置,这通常是很有用的。Spring Boot 允许在 Spring 环境中定义日志组。例如下面通过将 “tomcat” 组添加到 application.properties 中来定义它,定义后,可以用一行更改组中所有记录器的级别

    logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
    logging.level.tomcat=TRACE
    
  2. SpringBoot 包括以下预定义的日志记录组,可以开箱即用:

    NameLoggers
    weborg.springframework.core.codec, org.springframework.http, org.springframework.web
    sqlorg.springframework.jdbc.core, org.hibernate.SQL
    logging.level.web=INFO
    logging.level.sql=DEBUG
    
4、SpringBoot设置打印SQL

使用Mybatis的时候,SQL语句是DEBUG下才会打印,而这里我们只配置了INFO,所以想要查看SQL语句的话,有以下两种操作:

1、第一种把<root level="info">改成<root level="DEBUG">这样就会打印SQL,不过这样日志那边会出现很多其他消息

2、第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常INFO级别:

  • application.properties 全局配置

    logging.level.sql=DEBUG
    logging.level.org.mybatis=debug
    logging.level.com.xyz.*.dao=debug
    
  • logback-spring.xml 自定义配置文件配置

<configuration debug="true" scan="true" scanPeriod="60 seconds">
    <!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" />
    <logger name="org.hibernate.SQL" level="DEBUG" />
    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />

    <!--myibatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>
</configuration>
5、JAR启动设置日志输出级别

可以在启动的时候通过命令行参数指定日志输出级别(java -jar xxx.jar --level),相当于 logging.level.root 配置的根日志记录器。

java -jar .java-se-1.0-SNAPSHOT.jar --debug

4、Logback日志配置文件详解

  1. 可以直接在 SpringBoot 的全局配置文件中修改 SLF4J 的默认配置,也可以使用 SLF4J 实现框架的自己的配置文件。直接放置再类路径下即可

  2. 官网参考链接,根据日志记录系统的不同,各自的配置文件文件也不同:

    Logging System(日志系统)Customization(日志文件名称)
    Logbacklogback-spring.xml,logback-spring.groovy,logback.xml or logback.groovy
    Log4j2log4j2-spring.xml,log4j2.xml
    JDK (Java Util Logging)logging.properties
  3. 因为 SpringBoot 底层默认采用 SLF4J+Logback 的日志组合,也就是说如果在 src/main/resources 目录下放置其中任一类型的配置文件,SpringBoot便会自动进行使用。而SpringBoot官方推荐优先使用带有-spring的文件名配置(如有logback-spring.xml,则不会使用logback.xml)。若需要对配置文件名进行修改,或者希望把放到其它目录下,可以在application中通过logging.config属性来指定,如:

    logging.config=classpath:dev/logback-spring.xml
    
  4. 具体配置文件设置详情可参考上面的教程:【配置文件结构详解】

  5. 这是转载其他人整理的百分号属性参数说明大全:

        ********************************************************************************************************************
        参数        说明                                         举例                    输出显示媒介
        ********************************************************************************************************************
        %c          列出logger名字空间的全称,如果加上{<层数>},   假设当前logger的命名空间是"a.b.c"
                    则表示列出从最内层算起的指定层数的名字空间
                                                                %c                  a.b.c
                                                                %c{2}               b.c
                                                                %20c                (若名字空间长度小于20,则左边用空格填充)
                                                                %-20c               (若名字空间长度小于20,则右边用空格填充)
                                                                %.30c               (若名字空间长度超过30,截去多余字符)
                                                                %20.30c             (若名字空间长度小于20,则左边用空格填充;
                                                                                        若名字空间长度超过30,截去多余字符)
                                                                %-20.30c            (若名字空间长度小于20,则右边用空格填充;
                                                                                        若名字空间长度超过30,截去多余字符)
        ********************************************************************************************************************
        %C          列出调用logger的类的全名(包含包路径)         假设当前类是"org.apache.xyz.SomeClass"
                                                                %C                  org.apache.xyz.SomeClass
                                                                %C{1}               SomeClass
        %class
        ********************************************************************************************************************
        %d          显示日志记录时间,{<日期格式>}使用ISO8601定义的日期格式
                                                                %d{yyyy/MM/dd HH:mm:ss,SSS}     2005/10/12 22:23:30,117
                                                                %d{ABSOLUTE}        22:23:30,117
                                                                %d{DATE}            12 Oct 2005 22:23:30,117
                                                                %d{ISO8601}         2005-10-12 22:23:30,117
        ********************************************************************************************************************
        %F          显示调用logger的源文件名                     %F                   MyClass.java
        ********************************************************************************************************************
        %l          显示日志事件的发生位置,包含包路径、方法名、
                    源文件名,以及在代码中的行数                  %l                   com.a.b.MyClass.main(MyClass.java:168)
        ********************************************************************************************************************
        %L          显示调用logger的代码行                       %L                   129
        %line                                                  %line                129
        ********************************************************************************************************************
        %level      显示该条日志的优先级                         %level               INFO
        %p                                                     %p                   INFO
        ********************************************************************************************************************
        %m          显示输出消息                                 %m                  This is a message for debug.
        %message                                                %message            This is a message for debug.
        ********************************************************************************************************************
        %M          显示调用logger的方法名                       %M                   main
        ********************************************************************************************************************
        %n          当前平台下的换行符                           %n                   Windows平台下表示rn,UNIX平台下表示n
        ********************************************************************************************************************
        %p          显示该条日志的优先级                         %p                   INFO
        %level                                                 %level               INFO
        ********************************************************************************************************************
        %r          显示从程序启动时到记录该条日志时已经经过的毫秒数  %r                 1215
        ********************************************************************************************************************
        %t          输出产生该日志事件的线程名                    %t                   http-nio-8080-exec-10
        %thread                                                 %thread              http-nio-8080-exec-10
        ********************************************************************************************************************
        %x          按NDC(Nested Diagnostic Context,线程堆栈)顺序输出日志           假设某程序调用顺序是MyApp调用com.foo.Bar
                                                                %c %x - %m%n        MyApp - Call com.foo.Bar.
                                                                                    com.foo.Bar - Log in Bar
                                                                                    MyApp - Return to MyApp.
        ********************************************************************************************************************
        %X          按MDC(Mapped Diagnostic Context,线程映射表)
                    输出日志。通常用于多个客户端连接同一台服务器,
                    方便服务器区分是那个客户端访问留下来的日志。     %X{5}             (记录代号为5的客户端的日志)
        ********************************************************************************************************************
        %%          显示一个百分号                               %%                  %
        ********************************************************************************************************************
    

5、动态切换日志级别的几种方式

1、logback.xml 配置文件定时监控

修改 logback.xml 配置文件,定时监控配置变化情况。

<?xml version="1.0" encoding="UTF-8"?>
<!-- debug : 开启logback运行日志输出,true为始终输出,false为出错时才输出-->
<!-- scan :开启"热更新"-->
<!-- scanPeriod:"热更新"扫描周期,默认 60 seconds(60秒) 单位有:milliseconds、seconds、munites、hours,默认为milliseconds-->
<configuration scan="true" scanPeriod="60 seconds" debug="false"/>
  1. 如果是在本地 IDE 编辑器中测试,注意修改的是 classes 编译目录下的 logback.xml 文件,而不是 resources 目录下的源文件
  2. 鉴于现在都是微服务开发,分布式部署,而且为了方便管理,基本都是打包后部署在一些平台上面。直接修改日志配置文件不太现实
2、LoggerContext 切换日志输出级别

使用Controller接口动态修改日志输出级别,想设置什么级别,只需传参调接口即可,无论是设置root级别,还是指定包或类,不用重启服务能立马生效

package com.example.xyz;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogbackController {
    private static final Logger log = (Logger) LoggerFactory.getLogger(LogbackController.class);
    /**
     * 打印日志测试: http://localhost:8080/logback/logs
     */
    @GetMapping("logback/logs")
    public void logs() {
        log.error("hello, I am error!");
        log.warn("hello, I am warn!");
        log.info("hello, I am {}!", "info");
        log.debug("hello, I am debug!");
        log.trace("hello, I am trace!");
    }

    /**
     * logback 动态切换指定包或者类的日志输出级别,立即生效.
     *   http:localhost:8080/logback/updateLevel
     *   http:localhost:8080/logback/updateLevel?level=DEBUG
     *   http:localhost:8080/logback/updateLevel?level=DEBUG&clazz=com.xyz
     *
     * @param level :日志级别,可选值有:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF,默认为 debug。不区分大小写.
     * @param clazz :指定的包或者类路径,为空时默认设置全局(root)日志级别。路径不存在时,照样会设置这个路径,不会影响全局级别,这一点与 log4j2 不同.
     *              比如 org.springframework。
     * @return map  :返回指定路径的当前日志级别,root 表示全局级别
     */
    @GetMapping(value = "/logback/updateLevel")
    public String setLevel(String level, String clazz) {
        try {
            // 返回正在使用的 ILoggerFactory 实例.
            LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            /**
             * Logger getLogger(final String name):根据包路径或类路径获取日志记录器,"ROOT" 表示根记录器
             * Level toLevel(String sArg):转换为日志级别,如果转换失败,则默认为 debug
             * synchronized void setLevel(Level newLevel):为日志记录器设置日志输出级别,立即生效.
             */
            if (clazz == null || "".equals(clazz.trim())) {
                // 设置根日志记录器对象并设置日志输出级别
                loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.toLevel(level));
            } else {
                // 设置指定日志记录器并设置日志输出级别,路径不存在也没有关系,会返回它的日志记录器,然后设置输出级别
                loggerContext.getLogger(clazz).setLevel(Level.toLevel(level));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "fail";
        }
        return "success";
    }
}
3、SpringBoot Actuator 监控管理日志

使用SpringBoot Actuator监控管理日志的优点:

  • 无需编码,只需要引用Spring Boot Actuator监控依赖,然后开启访问端点 /loggers,即可轻松查看日志输出级别,并进行切换
  • 解耦日志框架,无论使用的 Logback 还是 Log4j2 ,都能轻松切换

1、引入pring Boot Actuator依赖

<!--监控和管理应用程序-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、开启日志访问端点: /loggers

management:
  endpoint:
    health:
      show-details: ALWAYS # 展示节点的详细信息
  endpoints:
    web:
      exposure:
        include: info,health,logfile,loggers # 指定公开的访问端点

3、查看级别

  • 发送 GET 请求,获取日志等级:http://localhost:8080/actuator/loggers
  • 返回的信息非常详细,包含了 ROOT,以及程序中各个包和类的日志级别
  • 其中 configuredLevel 表示配置级别,effectiveLevel 表示有效级别,configuredLevel 可能为 null,因为没有配置。

4、修改日志级别

  • 发送 POST 请求,设置指定包或者类(com.xyz)日志输出级别:http://localhost:8080/actuator/loggers/com.xyz
  • 发送 POST 请求,设置 root 全局日志输出级别:http://localhost:8080/actuator/loggers/root
  • 请求 Body 的内容格式:{“configuredLevel”:“error”}

7、参考文献 & 鸣谢

  • Spring Boot 2.x 日志配置 与集成 Logback 日志框架【CSDN 蚩尤后裔】https://blog.csdn.net/wangmx1993328/article/details/81044147
  • 万字详解logback日志框架,再没这么全的了【CSDN 程序新视界】https://blog.csdn.net/wo541075754/article/details/109193354
  • 学会这些Logback高级知识点,程序日志性能提高几十倍(上)【CSDN 陈皮的JavaLib】https://javalib.blog.csdn.net/article/details/113899664
  • java日志(三)–slf4j和logback使用【CSDN:panda-star】https://blog.csdn.net/chinabestchina/article/details/104743907
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值