LogBack架构

129 阅读5分钟
1. Logger, Appender 和 Layouts

Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用可以根据消息的类型以及日志的级别来打印日志。

Logger类作为 logback-classic 模块的一部分。Appender与Layouts接口作为 logback-core 的一部分。作为一个通用的模块,logback-core 没有 logger 的概念。

2. Logger上下文

我们可以使用Logger,根据开发人员设定的标准,对日志进行分类。在 logback-classic 中,分类是 logger 的一部分,每一个 logger 都依附在LoggerContext上,它负责产生 logger,并且通过一个树状的层级结构来进行管理。

一个 Logger 被当作为一个实体,它们的命名是大小写敏感的,并且遵循一下规则:

" logger1.logger2.logger3 ":logger1是logger2的父logger,是logger3的祖先logger。logger2是logger3的父logger。

root logger 作为 logger 层次结构的最高层。它是一个特殊的 logger,因为它是每一个层次结构的一部分。每一个 logger 都可以通过它的名字去获取。

获取root logger:

所有其它的 logger 通过org.clf4j.LoggerFactory 类的静态方法getLogger去获取,这个方法需要传入一个 logger 的名字。

Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME)

获取普通Logger:

如果logger名称相同,得到的是同一个logger实例。logback 不会严格限制 logger 的命名,你完全可以根据自己的喜好来。但是,根据类的全限定名来对 logger 进行命名,是目前最好的方式,没有之一。

// 通过类名获取,logger名即为该类名,推荐
private static Logger logger = LoggerFactory.getLogger(TestLogback.class);
// 通过logger名获取
private static Logger logger1 = LoggerFactory.getLogger("myLogger");

logger中的方法:

@Test
public void test(){
	logger.trace("trace....");
	logger.info("info....");
	logger.debug("debug....");
	logger.warn("warn....");
	logger.error("error....");
}
3. Appenter和Layout
Appenter:

有选择的启用或者禁用日志的输出只是 logger 的一部分功能。logback 允许日志在多个地方进行输出。站在 logback 的角度来说,输出目的地叫做 appender。appender 包括console、file、remote socket server、MySQL、PostgreSQL、Oracle 或者其它的数据库、JMS、remote UNIX Syslog daemons 中。

一个 logger 可以有多个 appender。

logger 通过 addAppender 方法来新增一个 appender。对于给定的 logger,每一个允许输出的日志都会被转发到该 logger 的所有 appender 中去。换句话说,appender 从 logger 的层级结构中去继承叠加性。叠加规则:

  • logger L 的日志输出语句会遍历 L 和它的子级中所有的 appender。这就是所谓的 appender 叠加性(appender additivity)
  • 如果 L 的子级 logger 为 P,且 P 设置了 additivity = false,那么 L 的日志会在 L 所有 的 appender 包括 P 本身的 appender 中输出,但是不会在 P 的子级 appender 中输出。
  • logger 默认设置 additivity = true。
Layout

用户既想自定义日志的输出地,也想自定义日志的输出格式。通过给 appender 添加一个 layout 可以做到。layout 的作用是将日志格式化,而 appender 的作用是将格式化后的日志输出到指定的目的地。PatternLayout 能够根据用户指定的格式来格式化日志。

4. Pattern标签

Pattern由文字文本和转换说明符的格式控制表达式组成 。您可以在其中自由插入任何文字文本。每个转换说明符都以百分号 '%' ,后跟可选的 格式修饰符、转换字和大括号之间的可选参数。转换字控制要转换的数据字段,例如记录器名称、级别、日期或线程名称。格式修饰符控制字段宽度、填充以及左对齐或右对齐。

image.png

image.png

5. 滚动策略
  • 基于时间的滚动策略 ch.qos.logback.core.rolling.TimeBasedRollingPolicy

  • 基于大小和时间的滚动策略 ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy

6. 过滤器Filter

过滤器是附加器的一个组件,它用于决定Appenter是否输出日志。一个Appenter可以包含一个或多个过滤器。

每个过滤器都会返回一个枚举值,可选的值:DENY、 NEUTRAL、ACCEPT

附加器根据过滤器返回值判断是否输出日志:

  • DENY : 不输出日志
  • ACCEPT : 输出日志
  • NEUTRAL : 中立,即不决定是否输出日志

常用的过滤器

  • LevelFilter(级别过滤器) : 实现类 ch.qos.logback.classic.filter.LevelFilter

  • ThresholdFilter(阈值过滤器):实现类 ch.qos.logback.classic.filter.ThresholdFilter

  • EvaluatorFilter(评估者过滤器) : 实现类 ch.qos.logback.core.filter.EvaluatorFilter

  • JaninoEventEvaluator过滤器: 实现类 ch.qos.logback.core.filter.EvaluatorFilter

  • TurboFilter涡轮过滤器

  • DuplicateMessageFilter 重复消息过滤器

#####7. 参数化日志

考虑到 logback-classic 实现了 SLF4J 的 Logger 接口,一些打印方法可以接收多个传参。这些打印方法的变体主要是为了提高性能以及减少对代码可读性的影响。

对于一些 Logger 如下输出日志:

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

会产生构建消息参数的成本,是因为需要将整数转为字符串,然后再将字符串拼接起来。

为了避免构建参数带来的损耗,可以在日志记录之前做一个判断,如下:

if(logger.isDebugEnabled()) {
  logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

在这种情况下,如果 logger没有开启 debug 模式,不会有构建参数带来的性能损耗。换句话说,如果 logger 在 debug 级别,将会有两次性能的损耗,一次是判断是否启用了 debug 模式,一次是打印 debug 日志。在实际应用当中,这种性能上的损耗是可以忽略不计的,因为它所花费的时间小于打印一条日志的时间的 1%。

更好的选择

有一种更好的方式去格式化日志信息。假设 entry 是一个 Object 对象:

Object entry = new SomeObject();
logger.debug("The entry is {}", entry);

使用两个参数的例子如下:

logger.debug("The new entry is {}, It replaces {}.", entry, oldEntry);

如果需要使用三个或三个以上的参数,可以采用如下的形式:

Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
8. 一个可用的logback配置文件示例

该配置文件将项目中的所有允许输出的日志,通过指定的格式,输出到到指定的文件中去:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 定义日志文件的存储地址 -->
    <property name="LOG_HOME" value="logs/sms-web" />

    <appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
        <file>${LOG_HOME}/testFile2.log</file>
        <encoder><!-- 必须指定,否则不会往文件输出内容 -->
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
        </encoder>
        <append>true</append>
        <prudent>false</prudent>
    </appender>

    <!-- 日志输出级别 -->
    <root level="DEBUG">
        <appender-ref ref="fileAppender" />
    </root>
</configuration>