Java核心技术之日志实现框架 Logback

Java核心技术之日志实现框架 Logback

官方文档

一、选择 LogBack 原因

  1. 执行效率更高
  2. 经过大量的测试
  3. SLF4J 配合使用零开销
  4. 文档齐全
  5. 使用 XML 或 Groovy 配置文件
  6. 配置文件自动重新加载
  7. 从 I/O 失败中优雅地恢复
  8. 通过配置可以自动删除旧日志文档
  9. 日志压缩
  10. 过滤器

二、最简使用教程

Step1 - 添加依赖

<!--SLF4J-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.25</version>
</dependency>

<!--LogBack-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>

Step2 - 创建配置文件

在这里插入图片描述

Step3 - 自定义 XML 配置文件

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

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

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

Step4 - 使用 SLF4J API 完成日志记录

private static void doSlf4jAPi() {
    Logger logger = LoggerFactory.getLogger(TestSlf4jApi.class);
    logger.info("Hello World");
    logger.info("hello, {}, hello, {}", "world", "java");
    logger.debug("debug");
    logger.warn("warn");
    logger.error("error");
    logger.error("当前LoggerFactory对象为:" + logger.getClass());
}
// OUTPUT
// 当前LoggerFactory对象为:class ch.qos.logback.classic.Logger

三、Logback 架构

配置文件语法

  • 标签名 大小写敏感
  • logger 标签
    • name : 必要属性。
    • level : 可选属性。
    • additivity : 可选属性。
    • logger 元素可以包含 0 个或多个 <appender-ref> 元素。被引用的 appender 都会添加到被命名的 logger 中。
  • root 标签
    • 仅支持 level 属性。
    • logger 标签相似, <root> 可以包含多个 <appender-ref> 元素。
  • 配置 Appenders
    • name : 必要属性。
    • class : 必要属性。表示要实例化的 appender 类的全限定名。
    • layout : 非必要属性。可包含0个或多个。
      • class
    • encoder : 非必要属性。可包含0个或多个。
    • filter : 非必要属性。可包含0个或多个。
      • class

在这里插入图片描述

  • Appenders 累加: 比如下面的 XML 配置将会在日志文件 myApp.log 中输出两行一模一样的日志。因为在 logger 以及 root 都配置了相同的 appender ,所以导致这种情况发生。怎么解决呢? 你可以根据项目需要把 appender 单独放在其中一个即可。或者通过设置 additivity=false 禁用从父类中继承行为。
<configuration scan="true" scanPeriod="10 seconds">

    <!--#1 定义输出目的地,当前配置为控制台-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--1-1 输出格式化-->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} = %msg%n</Pattern>
        </layout>
    </appender>

    <!--定义文件输出目的地
       encoder默认为PatternLayoutEncoder
    -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>myApp.log</file>
        <encoder>
            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} = %msg%n</Pattern>
        </encoder>
    </appender>

    <!--#2 logger,通过对特定的包名定义不同的level。有点像过滤器,对于name所定义的包,在日志输出时判断当前日志级别是否符合level,若不符合则舍弃-->
    <logger name="com.clarencezero" level="TRACE">
        <appender-ref ref="FILE" />
    </logger>

    <!--#3 定义root,root元素的level默认为DEBUG-->
    <root level="OFF">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>

    <!--等同于在<configuration debug="true">-->
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />

</configuration>
  • 设置上下文名称。 <contextName></contextName>
  • 定义变量: 使用 <property><variable> 标签定义,使用 ${} 完成属性获取
<configuration>
  <property name="USER_HOME" value="/home/sebastien" />
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${USER_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>

当定义标签众多,可以创建一个包含所有变量的单独文件可能更加方便。

<property file="src/main/java/chapters/configuration/variables1.properties" />
// variables1.properties
USER_HOME=/home/sebastien
  • 属性作用域
    • LOCAL SCOPE : 定义在配置文件中。
    • CONTEXT SCOPE : 上下文中的属性定义,与上下文共同存在。它在所有日志事件中都可用。
    • SYSTEM SCOPE : JVM

在替换期间,首先在LOCAL SCOPE作用域查找属性,然后在 CONTEXT_SCOPE 作用域中查找属性,最后在 SYSTEM SCOPE 作用域中查找属性。
可以通过指定 scope 来设置属性的范围:

<property scope="context" name="nodeId" value="firstNode" />
  • 默认的变量值: 使用 :- 表示,如 ${aName:-golden}
  • 动态定义属性
<configuration>

  <define name="rootLevel" class="a.class.implementing.PropertyDefiner">
    <shape>round</shape>
    <color>brown</color>
    <size>24</size>
  </define>
 
  <root level="${rootLevel}"/>
</configuration>
  • 条件配置
<if condition='property("HOSTNAME").contains("torino")'>
  <then>
    <appender name="CON" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
        <pattern>%d %-5level %logger{35} - %msg %n</pattern>
      </encoder>
    </appender>
    <root>
      <appender-ref ref="CON" />
    </root>
  </then>
</if>

3.1 三个模块

  • logback-core :基础模块,是其他两个模块的依赖。
  • logback-classic :相对于 Log4J 显著改进的版本。
  • logback-access :与 Servlet 容器集成,提供 Http-Access 日志功能。

3.2 Logger Context

单个 logger 附属于一个 LoggerContext ,这个 LoggerContext 负责制造 loggers 并安排在一个类似树的层次结构中。

3.3 命名层次

名为 com.foo 的记录器是名为 com.foo.Bar 记录器的父记录器。 org.slf4j.Logger.ROOT_LOGGER_NAME 为每个记录器(Logger)的父类(有点类似 Java 的 Class 类)。Logger 接口中一些基本方法:

package org.slf4j; 
public interface Logger {

  // Printing methods: 
  public void trace(String message);
  public void debug(String message);
  public void info(String message); 
  public void warn(String message); 
  public void error(String message); 
}

3.4 级别

记录器可以被分配一个级别( Level ), ch.qos.logback.classic 定义了以下几个级别:

  • TRACE
  • DEBUG
  • INFO
  • WARN
  • ERROR

日志级别顺序 TRACE < DEBUG < INFO < WARN < ERROR
Levelfinal 类,不能被实例化,因为存在一个更加灵活的 Marker 接口。
如果没有为 logger 分配级别,那么它将从最近的具有分配级别的父类继承一个。默认情况下,根记录器分配的级别为 DEBUG
基本选择原则:如果 p >= q ,则启用向具有有效级别 q 的记录器发出的级别 p 的日志请求。

在这里插入图片描述

横坐标表示 Level ,纵坐标表示请求类型,比如当 Level=INFO 时,则会打印 INFO、WARN、ERROR 这三种级别的日志信息。可以使用以下代码测试:

public class TestLogbackLevel {
    @Test
    public void testLevel() {
        ch.qos.logback.classic.Logger logger =
                (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
        // set its Level to INFO. The setLevel() method requires a logback logger
        logger.setLevel(Level.INFO);

        Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");

        // This request is enabled, because WARN >= INFO
        logger.warn("Low fuel level.");

        // This request is disabled, because DEBUG < INFO.
        logger.debug("Starting search for nearest gas station.");

        // The logger instance barlogger, named "com.foo.Bar",
        // will inherit its level from the logger named
        // "com.foo" Thus, the following request is enabled
        // because INFO >= INFO.
        barlogger.info("Located nearest gas station.");

        // This request is disabled, because DEBUG < INFO.
        barlogger.debug("Exiting gas station search");
    }
}

3.5 △ Appenders

输出目的地(output destination)被称为 appender 。以下这些都可以做为 Appender :

  • 控制台(console)
  • 文件(files)
  • 远程socket服务(remote socket servers)
  • MySQL、PostgreSQL、Oracle and other databases
  • JMS and remote UNIX Syslog daemons

一个 Appender 可以绑定多个 logger
Appender 规则如下:
Logger L 的日志语句输出将转到 L 中所有的 Appenders 及其祖先。可以通过设置 additivity 标志为 false 禁用此规则。

在这里插入图片描述


通常,用户不仅希望自定义输出目的地,还希望自定义输出格式。这可以通过将 布局(layout)appender 关联来实现。

3.6 Layout

负责根据用户自定义格式化日志记录请求PatternLayout 是标准日志发布一部分,允许用户根据类似于 C 语言 printf 函数的转换模式指定输出格式。比如:

"%-4relative [%thread] %-5level %logger{32} - %msg%n"

[main] DEBUG manual.architecture.HelloWorld2 - Hello world.

3.7 △Appender

3.7.1 Appender相关接口

public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable {

  public String getName();
  public void setName(String name);
  void doAppend(E event);
  
}

重要的方法是 doAppender ,实现了 FilterAttachable ,因此可以将一个或多个过滤器添加到 Appender 实例中。 Appender 最终输出日志事件,日志格式化需要代理给 LayoutEncoder 对象实现。每个 layout/encoder 绑定有且只有一个 appender

3.7.2 AppenderBase

ch.qos.logback.core.AppenderBase 是实现 Appender 接口的抽象类,它提供所有 Appender 共享的基本功能,比如获取/设置它们的名称, activation 状态, layout 以及 filters

3.7.3 Logback-Core

Logback-core 为构建其他 logback 模块奠定了基础。

OutputStreamAppender

将事件添加到 java.io.OutputStream 。此类提供其他 appenders 构建的基本服务。

在这里插入图片描述

ConsoleAppender

使用 System.outSystem.err 输出日志。

FileAppender

FileAppenderOutputStreamAppender 的子类,它将日志事件附加到文件中。目标文件由 File 选项指定。如果文件已经存在,则根据追加属性的值将其追加或截断。

属性名称类型描述
appendbooleantrue : 添加到已存在文件的末尾,默认值。
false : 任何已存在的文件都会被截断。
encoderEncoder
fileString要写入的文件名称。如果文件不存在,则自动创建。
prudentboolean默认值: false
true : FileAppender 完全写入指定文件。由于依赖锁实现锁而实现安全写入,这样会导致吞吐量下降。

默认情况下,每个日志事件都会立即刷新到底层输出流。这种默认方式比较安全,但如果想提高吞吐量,可以设置为 false 。例子:

<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>testFile.log</file>
    <append>true</append>
    <!-- set immediateFlush to false for much higher logging throughput -->
    <immediateFlush>true</immediateFlush>
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
        
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

通过时间戳命令文件

<configuration>

  <!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
       the key "bySecond" into the logger context. This value will be
       available to all subsequent configuration elements. -->
  <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <!-- use the previously created timestamp to create a uniquely
         named log file -->
    <file>log-${bySecond}.txt</file>
    <encoder>
      <pattern>%logger{35} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>
  • <timestamp> 接收两个必要属性,分别是 keydatePattern 以及一个 timeReference 属性。
    • datePattern 表示用于当前时间(解析配置文件的时间)转换为字符串的日期模式。遵循 SimpleDateFormat 约定。
△RollingFileAppender

RollingFileAppender 扩展 FileAppender ,提供滚动日志文件功能。例如,可以将日志记录到一个名为 log.txt 文件中,一旦满足某个条件,就将其日志记录目标更忙为另一个文件。
有两个重要的子组件与RollingFileAppender 交互:

  • RollingPolicy : 负责执行滚动所需的操作
  • TriggeringPolicy : 确定是否滚动以及什么时候滚动,什么时候触发时机。
属性名称类型描述
fileString
appendboolean
rollingPolicyRollingPolicy在发生滚动时将决定 RollingFileAppender 行为的组件
triggeringPolicyTriggeringPolicy何时激活滚动过程
prudentboolean谨慎模式

滚动策略

RollingPolicy 负责滚动过程,包括 文件移动重命名

public interface RollingPolicy extends LifeCycle {

    public void rollover() throws RolloverFailure;
    public String getActiveFileName();
    public CompressionMode getCompressionMode();
    public void setParent(FileAppender appender);
}
  • rollover() : 完成归档当前日志文件的工作。
  • getActiveFileName() : 返回当前日志文件的文件名(将活动日志写入其中)
△基于时间滚动策略 – TimeBasedRollingPolicy

基于 时间 的滚动策略。可能是最受欢迎的日志策略。定义一个基于 时间 的滚动策略。比如按 等。

属性名称类型描述
fileNamePatternString包含文件的名称加上 %d{时间格式}
默认格式为 yyyy-MM-dd ,如果存在多个,需要使用 aux 参数指定辅助标签。
maxHisotoryint归档日志保存最大时间,异步删除旧的日志文件。
totalSizeCapint所有归档文件总大小。当超出上限时,将异常删除最早的归档文件。若 maxHistorytotalSizeCap 同时设置,则 maxHistroy 首先被应用
cleanHistoryOnStartboolean在启动应用程序时执行档案删除。默认为 false

以下是效果说明

文件名称模式滚动调度
foo.%d每日滚动(午夜)
/wombat/%d{yyyy/MM}/foo.txt每月开始
/wombat/foo.%d{yyyy-ww}.log每周第一天
/wombat/foo%d{yyyy-MM-dd_HH}.log每小时
/wombat/foo%d{yyyy-MM-dd_HH-mm}.log每分钟
/wombat/foo%d{yyyy-MM-dd_HH-mm, UTC}.log每分钟
/foo/%d{yyyy-MM,aux}/%d.log每日更新。档案位于包含年份和月份的文件夹下
/wombat/foo.%d.gzTimeBasedRollingPolicy 支持自动文件压缩, fileNamePattern.gz.zip 结尾。

滚动时间不是基于服务器时间,而是基于日期事件到达时间。
下面是 RollingFileAppenderTimeBasedRollingPolicy 的示例配置。

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logFile.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- 每日滚动生成日志文件 -->
      <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>

      <!-- 保留30天历史记录,总大小上限为3GB -->
      <maxHistory>30</maxHistory>
      <totalSizeCap>3GB</totalSizeCap>

    </rollingPolicy>

    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender> 

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

△基于大小和时间的滚动策略 – SizeAndTimeBasedRollingPolicy

有时候希望基本按日期归档文件,但同时限制每个日志文件的大小。可以使用 SizeAndTimeBasedRollingPolicy 策略达到目的。

<configuration>
  <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>mylog.txt</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <!-- rollover daily -->
      <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
       <!--每个日志文件最大容量为100MB,保留60天,日志总量最大为20GB-->
       <maxFileSize>100MB</maxFileSize>    
       <maxHistory>60</maxHistory>
       <totalSizeCap>20GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>


  <root level="DEBUG">
    <appender-ref ref="ROLLING" />
  </root>

</configuration>

注意
%i%d{} 都是必要的。每当当前日志文件大小超过 maxFileSize 时,都会使用一个不断增的索引(从0开始)将其归档。

固定时间窗口滚动策略 – FixedWindowRollingPolicy

固定窗口滚动策略。以下是其可用属性:

属性名称类型描述
minIndexint窗口下限
maxIndexint窗口上限
fileNamePatternString
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
  <fileNamePattern>tests.%i.log.zip</fileNamePattern>
  <minIndex>1</minIndex>
  <maxIndex>3</maxIndex>
</rollingPolicy>

3.7.4 触发策略概述

TriggeringPolicy 定义指示 RollingFileAppender 是否滚动。接口定义如下:

public interface TriggeringPolicy<E> extends LifeCycle {
  // activeFile: 正在活动的文件,event: 日志事件
  public boolean isTriggeringEvent(final File activeFile, final <E> event);
}

3.7.5 AsyncAppender

异步 记录 ILoggingEvents 事件,它只做事件分配器,因此必须引用另一个 appender 以执行异步写入操作。 事件 存储在 BlockingQueue 队列,由 AsyncAppender 创建的工作线程从队列头部获取日志事件,并将它们分派到 AsyncAppender 绑定的单个 appender 中,这个 appender 是真正用来处理事件,包括格式化以及完成写入操作。

注意: 
在队列容量超过 80% 时,AsyncAppender 将会删除 TRACE、 DEBUG 和 INFO  级别的事件。这种策略以损失事件为代价换取吞吐率。

当应用程序关闭或重新部署时, AsyncAppender 必须得以停止。可以通过停止 LoggerContext 来实现,这将关闭所有 appender ,包括任何 AsyncAppender 实例。 AsyncAppender 将等待工作线程最大刷新时间 maxFlushTime ,达到设定的时间,不管工作线程有没有完成工作都会被强制关闭。
可以通过向 JVM 注册 shutdown hook 以正确关闭 LoggerContext 。

属性名称类型描述
queueSizeint阻塞队列最大容量,默认值: 256
discardingThresholdint丢弃阈值,默认值: 20%
includeCallerDataboolean
maxFlushTimeint毫秒为单位指定最大队列刷新超时
neverBlockboolean
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>%logger{35} - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE" />
  </appender>

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

3.8 Encoders

Encoders 负责将事件转换为 字节数组(byte array) ,并将字节数组写入 Outputstream
目前, PatternLayoutEncoder 是真正有用的 Encoder

public interface Encoder<E> extends ContextAware, LifeCycle {

    /**
     * Get header bytes. This method is typically called upon opening of 
     * an output stream.
     * 
     * @return header bytes. Null values are allowed.
     */
    byte[] headerBytes();

    /**
     * Encode an event as bytes.
     *  
     * @param event
     */
    byte[] encode(E event);
                    
    /**
     * Get footer bytes. This method is typically called prior to the closing 
     * of the stream where events are written.
     * 
     * @return footer bytes. Null values are allowed.
     */
    byte[] footerBytes();
}

3.8.1 LayoutWrappingEncoder

public class LayoutWrappingEncoder<E> extends EncoderBase<E> {

  protected Layout<E> layout;
  private Charset charset;
 
   // encode a given event as a byte[]
   public byte[] encode(E event) {
     // 将用户事件转换为字符串  
     String txt = layout.doLayout(event);
     // 根据用户选择的字符集编码转换为字节
     return convertToBytes(txt);
  }

  private byte[] convertToBytes(String s) {
    if (charset == null) {
      return s.getBytes();
    } else {
      return s.getBytes(charset);
    }
  } 
}

3.8.2 PatternLayoutEncoder

这是 LayoutWrappingEncoder 的一个扩展,仅限于包装 PatternLayout 的实例。

3.9 Layout

简单来说就是将用户事件格式化为字符串。

3.9.1 配置自定义 layout

  1. 继承 LayoutBase<ILoggingEvent>
public class MySampleLayout extends LayoutBase<ILoggingEvent> {

  public String doLayout(ILoggingEvent event) {
    StringBuffer sbuf = new StringBuffer(128);
    sbuf.append(event.getTimeStamp() - event.getLoggingContextVO.getBirthTime());
    sbuf.append(" ");
    sbuf.append(event.getLevel());
    sbuf.append(" [");
    sbuf.append(event.getThreadName());
    sbuf.append("] ");
    sbuf.append(event.getLoggerName();
    sbuf.append(" - ");
    sbuf.append(event.getFormattedMessage());
    sbuf.append(CoreConstants.LINE_SEP);
    return sbuf.toString();
  }
}
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="chapters.layouts.MySampleLayout" />
    </encoder>
  </appender>

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

3.9.2 PatternLayout

PatternLayout 接受一个日志事件并返回 string 。这个字符串可以通过调整 PatternLayout 的转换模式来定制。PatternLayout 的转换模式与 C 语言()的 printf() 函数的转换模式密切相关,每个转换说明符以 % 开始,后面跟着可选的 格式修饰符转换字 和大括号可选参数。
比如 %logger{10} : logger 是转换词, 10 是选项。

转换字效果
logger{length}mainPackage.sub.sample.Bar
%logger{0} : Bar
%logger{5} : m.s.s.Bar
%logger{26} : mainPackage.sub.sample.Bar
d{pattern}
date{pattern}
d{pattern, timezone} date{pattern, timezone}
%d : 2006-10-20 14:06:49,812
%date : 2006-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
m/msg/message输出与日志记录事件关联的信息
n输出平台相关的换行分隔符。 \n\r\n
p/le/level输出日志事件级别
r/relative输出从应用程序启动到创建日志事件之间经过的毫秒数
t/thread输出生成日志记录事件的线程名称
X{key:-defaultVal}
mdc{key:-defaultVal}
输出与生成日志记录事件的线程关联的 MDC
%MDC{userid} 则通过 key=userid 查找 MDC 对应的 value 值。如果为空,则输出 :- 提供了默认值,如果没有指定默认值,则输出空字符串。
xEx{depth}
xException{depth}
xThrowable{depth}
xEx{depth, evaluator-1, …, evaluator-n}
xException{depth, evaluator-1, …, evaluator-n}
xThrowable{depth, evaluator-1, …, evaluator-n}
堆栈深度。默认输出完整的堆栈。
- short : 打印第一行
- full : 打印完整的堆栈跟踪
- any integer : 打印堆栈跟踪的给定行数
property{key}从上下文获取,获取不到则从系统属性中查找

3.10 格式化修饰符

借助修饰符,可以改变每个数据字段的 最小最大宽度
可选的格式修饰符放在 百分比符号转换字符或单词之间 ,即 % 格式修饰符 转换字符

格式修饰符含义描述
- 最小字段宽度(十进制)左对齐%-10thread : 左对齐,小于20则为空格
%.30 : 字符串长于30,从头截断
%20.30 : 小于20,左边添加空格,长于30,从头截断
%.-30 : 从后面截断

3.10.1 分组

%-30(%d{HH:mm:ss.SSS} [%thread]) %-5level %logger{32} - %msg%n

少于 30 个字符串进行右填充。

3.11 过滤器

在 logback-classic 中,过滤器可以添加到 Appender 实例中。通过向附加程序添加一个或多个过滤器,可以按任意标准过滤事件,例如日志消息的内容、 MDC 的内容、一天的时间或日志事件的任何其他部分。

public class SampleFilter extends Filter<ILoggingEvent> {

  @Override
  public FilterReply decide(ILoggingEvent event) {    
    if (event.getMessage().contains("sample")) {
      return FilterReply.ACCEPT;
    } else {
      return FilterReply.NEUTRAL;
    }
  }
}
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

    <filter class="chapters.filters.SampleFilter" />

    <encoder>
      <pattern>
        %-4relative [%thread] %-5level %logger - %msg%n
      </pattern>
    </encoder>
  </appender>
        
  <root>
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

3.12 MDC

3.12.1 相关方法

public class MDC {
  //Put a context value as identified by key
  //into the current thread's context map.
  public static void put(String key, String val);

  //Get the context identified by the key parameter.
  public static String get(String key);

  //Remove the context identified by the key parameter.
  public static void remove(String key);

  //Clear all entries in the MDC.
  public static void clear();
}

3.12.2 XML 引用

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 
  <layout>
    <Pattern>%X{first} %X{last} - %m%n</Pattern>
  </layout> 
</appender>

3.12.3 MDC 高级使用

put()get()MDC 操作只影响当前线程和子线程。其他线程中的 MDC 不受影响。底层是由 ThreadLocal 实现。

// ch.qos.logback.classic.util.LogbackMDCAdapter
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();

四、Logger 日志输出过程

  1. 获取过滤器链决策:如果它存在,则调用 TurboFilter 链,可以设置 context-wide 阈值或根据每个日志记录请求相关信息(如 Marker、Level, Logger, message, or the Throwable )过滤掉某些事件。如果过滤器链返回结果有 FilterReply.DENY,删除日志记录请求 ,如果是 FilterReply.NEUTRALdd,继续下一步 ,回复 FilterReply.ACCEPT,跳过下一步,直接第三步
  2. 应用基本选择规则:logback 将记录器的有效级别与请求级别进行比较。如果根据这个测试禁用了日志记录请求,那么 logback 将删除该请求,而不需要进一步处理。否则,它将继续进行下一步。
  3. 创建 LoggerEvent 对象:如果请求在前面的过滤器中幸存下来,logback 将创建一个 ch.qos.logback.classic.LoggingEvent 对象, LoggingEvent 对象包含请求的所有相关参数。例如请求的日志记录器、请求级别、消息本身、可能随请求一起传递的异常、当前时间、当前线程、关于发出日志记录请求的类和 MDC 的各种数据。
  4. 调用 appenders:在创建 LoggingEvent 对象之后,logback 将调用所有适用的附加程序的 doAppend() 方法,即从 logger 上下文继承的附加程序。
  5. 格式化输出:格式化输出属于被调用的 appender 的职责。
  6. 发送 LoggingEvent :日志事件完全格式化之后,由每个 Appender 将其发送到目的地。

以下为其 XML 流程图:
在这里插入图片描述

  • ConsoleAppender :将向控制台输出日志请求,并缩短日志记录器名称,以增加控制台窗口的空间,而不会失去可读性。
  • RollingFileAppender :将日志记录事件发送给一个名为 logFile.log 的文件。并且每分钟更新一次活动文件。旧的文件将会被重合名并压缩为 zip 文件。
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
  <file>testFile.log</file>
  ...
  <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>%msg%n</pattern>
  </encoder>
</appender> 

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
  <file>testFile.log</file>
  ...
  <!-- encoders are assigned the type 
       ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
  <encoder>
    <pattern>%msg%n</pattern>
  </encoder>
</appender>   

将默认配置文件位置设置为系统属性
java -Dlogback.configurationFile=/path/to/config.xml chapters.configuration.MyApp1

五、常用 Logback 日志配置

5.1 日志版本:

  • logback-core : 1.2.3
  • logback-class : 1.2.3
  • slf4j-api : 1.7.30

5.2 基于 TimeBasedRollingPolicy 滚动策略

一般是按每天进行日志滚动( %d{yyyy-MM-dd} )。无论生成文件有多大,当天的日志全部归为一个日志文件。

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

<configuration scan="true">
    <property name="LOG_HOME" value="D://log"/>
    <property name="APP_NAME" value="hospital" />

    <!-- #1 控制台日志输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--1-1 输出格式化-->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%15thread] %-5level %logger{36} - %msg%n</Pattern>
        </layout>
    </appender>

    <!--#2 滚动日志输出-->
    <appender name="ROLLINGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${APP_NAME}.log</file>
        <!--配置滚动策略: 基于时间滚动(%d{yyyy-MM-dd}每天生成单独日志文件)-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--保留天数: 30-->
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%15thread] %-5level %logger{36} = %msg%n</Pattern>
        </encoder>
    </appender>

    <!--#3 定义level-->
    <logger name="com.clarencezero" level="TRACE">
    </logger>

    <!--#4 关联appender-->
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ROLLINGFILE"/>
    </root>
</configuration>

5.3 基于SizeAndTimeBasedRollingPolicy 大小和时间的滚动策略

如果单独基于时间进行每日日志滚动生成,这样会导致单个日志非常大,所以应该把时间和空间大小结合起来生成日志文件。
在这里插入图片描述

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

<configuration scan="true">
    <property name="LOG_HOME" value="D://log"/>
    <property name="APP_NAME" value="hospital"/>

    <!-- #1 控制台日志输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--1-1 输出格式化-->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%15thread] %-5level %logger{36} - %msg%n</Pattern>
        </layout>
    </appender>


    <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- rollover daily -->
            <fileNamePattern>${LOG_HOME}/%d{yyyy-MM, aux}/${APP_NAME}-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
            <!--每个日志文件最大容量为100MB,保留60天,日志总量最大为20GB-->
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>60</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%15thread] %-5level %logger{36} = %msg%n</Pattern>
        </encoder>
    </appender>


    <!--#3 定义level-->
    <logger name="com.clarencezero" level="TRACE">
    </logger>

    <!--#4 关联appender-->
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ROLLING"/>
    </root>
</configuration>
  • SizeAndTimeBasedRollingPolicy : 基于日志文件大小和时间进行滚动策略
  • ${LOG_HOME}/%d{yyyy-MM, aux}/${APP_NAME}-%d{yyyy-MM-dd}.%i.txt : 每天生成日志文件可能有几十个,把这几十个日志文件放入单独的以 yyyy-MM 命名的日志文件夹中。

5.4 设置配置文件热部署

<configuration scan="true" scanPeriod="30 seconds" > 
  ...
</configuration> 
  • 默认时间间隔: 一分钟
  • 默认时间单位: 毫秒
  • 实现原理 : 当 scan="ture" 时,将会安装 ReconfigureOnChangeTask ,此任务在单独的单独的线程中运行并检查配置文件是否已更改。

5.5 启用堆栈跟踪数据打包

在版本 1.1.4 中,默认是禁用打包堆栈数据的。因为堆栈计算成本相当高,特别是在频繁引发异常的应用程序中。比如:

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]

可以使用如下配置开启:

<configuration packagingData="true">
  ...
</configuration>
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.setPackagingDataEnabled(true);

5.6 关闭资源

通过 JVMshutdown hook 关闭日志和翻译资源是一种方便的方法:

<configuration debug="true">
   <!-- in the absence of the class attribute, assume 
   ch.qos.logback.core.hook.DefaultShutdownHook -->
   <shutdownHook/>
  .... 
</configuration>

默认的名称为 DefaultShutdownHook ,将在指定的延迟(默认为0)之后停止 logback context 。在30秒内任何在后台运行的日志文件压缩任务都要完成。在 Web 环境中, webShutdownHook 将会自动安装,这使得 <shutdownHook/> 没必要显示配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值