SLF4J 和 Logback 原理简介

491 阅读5分钟

日志接口(日志门面)

接口用于定制规范,可以有多个实现,使用时是面向接口的(导入的包都是 SLF4J 的包或者是 JCL 的包,而不是具体某个日志框架中的包),即直接和接口交互,不直接使用实现,所以当需要更换实现的时候,直接更换实现就可以了,而不用更改代码中的日志相关代码。

比如:SLF4J 定义了一套日志接口,项目中使用的日志框架是 log4j,开发中调用的所有接口都是 SLF4J 的,不直接使用 log4j,项目应用调用 SLF4J 的接口,SLF4J 的接口去调用 log4j 的实现,整个应用程序并没有直接使用 log4j,当项目需要更换更加优秀的日志框架时(如 logback)只需要引入 logback 的 jar 和 logback 对应的配置文件即可,完全不用更改 Java 代码中的日志相关的代码 ,也不用修改日志相关的类的导入的包。

因此日志门面的使用,为后续具体日志系统的实现更换带来了方便。

Common Logging 和 SLF4J 都是日志的接口,供用户使用,而没有提供实现,Log4j,JUL,logback 等等才是日志的真正实现。

日志库 Log4j,JUL,logback 是互相不兼容的,没有共同的 Interface,所以 commons-logging、SLF4J 通过适配器模式,抽象出来一个共同的接口,然后根据使用的具体日志框架来实现日志。当我们调用日志接口时,接口会自动寻找恰当的实现,返回一个合适的实例给我们服务。这些过程都是透明化的,用户不需要进行任何操作。

SLF4J 简介

SLF4J 主要通过适配器层和桥接模式。适配器是指 SLF4J 可以通过适配器层,对接底层日志框架,进行日志记录。如果我们依赖的包包含多种日志框架,SLF4J 会通过桥接模式,劫持第三方日志框架的数据,并重定向至 SLF4J 。

Logback 简介

基本架构

Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。logback 允许日志在多个地方进行输出。layout 的作用是将日志格式化,而 appender 的作用是将格式化后的日志输出到指定的目的地。

  • Logger:Logger 包含日志级别和名称两个基本属性。每一个 logger 都依附在 LoggerContext 上,它负责产生 logger,并且通过一个树状的层级结构来进行管理。
  • Appender :logback 将写入日志事件的任务委托给一个名为 appender 的组件. 所有的 appender 必须实现 Appender 接口,它的核心方法为 doAppend(LoggingEvent event),负责将日志事件进行格式化,然后输出到对应的设备上。如支持如输出到 控制台的 ConsoleAppender 和负责将日志输出到文件的 FileAppender 等。
  • LoggingEvent:日志事件,包含了日志请求所有相关的参数,请求的 logger,日志请求的级别,日志信息,与日志一同传递的异常信息,当前时间,当前线程,以及当前类的各种信息和 MDC。
  • Filter:过滤器,根据每一条相关的日志请求信息,例如:Marker, Level, Logger, 消息,Throwable ****来过滤某些事件。如果过滤器链的响应是 FilterReply.DENY,那么这条日志请求将会被丢弃。如果是 FilterReply.NEUTRAL,则会继续执行下一步。
  • Encode/Layout:encoder 将日志事件转换为字节数组,同时将字节数组写入到一个 OutputStream 中。encoder 在 logback 0.9.19 版本引进。在之前的版本中,大多数的 appender 依赖 layout 将日志事件转换为 string,然后再通过 java.io.Writer 写出。layout 在日志事件写出时不能控制日志事件,不能将日志事件批量聚合。与之相反的是,encoder 不但可以完全控制字节写出时的格式,而且还可以控制这些字节什么时候被写出。

执行流程

  1. Logger 获取

    SLF4J 初始化,我们在获取 LogFactory.getLogger 时,会自动触发初始化逻辑。需要关注的是,如果获取的 Logger 不存在,会进行初始化,并放入缓存中。执行 getLogger 时,需要初始化 provider,这个时候就会找到 logbackProvider,然后调用 LogbackServiceProvider 中的 initialize 方法,进行 LogbackContext 初始化,初始化之后,即可根据需求,生成或者获取所需的 Logger。

  2. 日志打印

  • 第一步:获取过滤器链

如果存在,则 Turbo 过滤器会被调用,Turbo 过滤器会设置一个上下文的阀值,或者根据每一条相关的日志请求信息进行过滤。

  • 第二步:过滤

在这步,logback 会比较有效级别与日志请求的级别,如果日志请求被禁止,那么 logback 将会丢弃掉这条日志请求,并不会再做进一步的处理,否则的话,则进行下一步的处理。

  • 第三步:创建一个 LoggingEvent 对象

如果日志请求通过了之前的过滤器,logback 将会创建一个 LoggingEvent 对象,这个对象包含了日志请求所有相关的参数。

  • 第四步:调用 appender

在创建了 LoggingEvent 对象之后,logback 会调用所有可用 appender 的 doAppend() 方法。这些 appender 继承自 loggerContext。所有的 appender 都继承了 AppenderBase 这个抽象类,并实现了 doAppend() 这个方法,该方法是线程安全的。AppenderBase 的 doAppend() 也会调用附加到 appender 上的自定义过滤器。

  • 第五步:格式化输出

被调用的 Appender 负责格式化 LoggingEvent。但是,有些 Appender 将格式化 LoggingEvent 的任务委托给 Layout。Layout 将 LoggingEvent 实例格式化为一个字符串并返回。但需要注意的是,某些 Appender(例如 SocketAppender) 并不会把 LoggingEvent 转化为一个字符串,而是进行序列化。因此,它们没有并且也不需要 Layout。

  • 第六步:发送 LoggingEvent

当日志事件被完全格式化之后将会通过每个 appender 发送到具体的目的地。