第 5 章编码器

**** 这天采取行动**确保他们拥有他们想要的所有优先事项,并向我报告已经完成。

— 1941 年 10 月,为了回应艾伦·图灵(Alan Turing)和他在布莱奇利公园(Bletchley Park)的密码分析家同事签署的更多资源请求,致使黑斯廷斯·伊斯梅(将军)教堂

什么是编码器

编码器负责将事件转换为字节数组,并将该字节数组写出为OutputStream。编码器是在 logback 版本 0.9.19 中引入的。在以前的版本中,大多数追加程序都依赖于布局将事件转换为字符串并使用java.io.Writer将其写出。在以前的版本中,用户会将PatternLayout嵌套在FileAppender内。自从 0.9.19 重新logback以来,FileAppender和子类期待编码器,不再需要布局

为什么要进行重大变革?

布局将在下一章中详细讨论,它只能将事件转换为字符串。此外,由于布局无法控制何时写入事件,因此布局无法将事件聚合为批。与此相比,编码器不仅可以完全控制要写出的字节的格式,还可以控制何时(以及是否)可以写出这些字节。

目前,PatternLayoutEncoder是唯一 true 有用的编码器。它只包装一个完成大多数工作的PatternLayout。因此,似乎除了不必要的复杂性之外,编码器并没有带来太多好处。但是,我们希望随着新型强大编码器的出现,这种印象会改变。

Encoder interface

编码器负责将进入的事件转换为字节数组**,并将结果字节数组写到适当的OutputStream上。因此,编码器完全控制什么字节以及何时将字节写入拥有者所维护的OutputStream。这是Encoder interface:

package ch.qos.logback.core.encoder;

public interface Encoder<E> extends ContextAware, LifeCycle {

   /**
   * This method is called when the owning appender starts or whenever output
   * needs to be directed to a new OutputStream, for instance as a result of a
   * rollover.
   */
  void init(OutputStream os) throws IOException;

  /**
   * Encode and write an event to the appropriate {@link OutputStream}.
   * Implementations are free to defer writing out of the encoded event and
   * instead write in batches.
   */
  void doEncode(E event) throws IOException;

  /**
   * This method is called prior to the closing of the underling
   * {@link OutputStream}. Implementations MUST not close the underlying
   * {@link OutputStream} which is the responsibility of the owning appender.
   */
  void close() throws IOException;
}

如您所见,Encoder接口由几种方法组成,但是令人惊讶的是,使用这些方法可以完成许多有用的事情。

LayoutWrappingEncoder

在 0.9.19 版本的 Logback 之前,许多附加程序都依赖 Layout 实例来控制日志输出的格式。由于存在大量基于布局接口的代码,因此我们需要一种编码器与布局互操作的方法。 LayoutWrappingEncoder弥合了编码器和布局之间的差距。它实现了编码器接口,并包装了一个布局,该布局委派了将事件转换为字符串的工作。

以下是LayoutWrappingEncoder类的摘录,说明了如何委派给包装的布局实例。

package ch.qos.logback.core.encoder;

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);
    }
  } 
}

doEncode()方法首先使包装的布局将传入事件转换为字符串。根据用户选择的字符集编码,将所得的文本字符串转换为字节。

PatternLayoutEncoder

鉴于PatternLayout是最常用的布局,因此 logback 通过PatternLayoutEncoder满足了这种常见用例,LayoutWrappingEncoder的扩展仅限于包装PatternLayout的实例。

从 0.9.19 版开始,无论何时FileAppender或其子类之一配置了PatternLayout,都必须使用PatternLayoutEncoder来代替。 logback错误代码中的相关条目中对此进行了说明。

immediateFlush property

从 1.2.0 版开始,backupFlush 属性是封闭的 Appender 的一部分。

输出模式字符串作为标题

为了便于分析日志文件,logback 可以在日志文件顶部插入用于日志输出的模式。此功能默认为“禁用”。可以通过将相关PatternLayoutEncoder的 outputPatternAsHeader 属性设置为'true'来启用它。这是一个例子:

<appender name="FILE" class="ch.qos.logback.core.FileAppender"> 
  <file>foo.log</file>
  <encoder>
    <pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern>
    <outputPatternAsHeader>true</outputPatternAsHeader>
  </encoder> 
</appender>

这将导致输出类似于日志文件中的以下内容:

#logback.classic pattern: %d [%thread] %-5level %logger{36} - %msg%n
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hello world
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hi again
...

以“#logback.classic pattern”开头的行是新插入的 pattern 行。