logback_2-Encoder,Layout

蔚丰
2023-12-01

一 What is an encoder

Encoders就是把 LoggingEvent转化为字节数组并向 OutputStream(输出流)写出的这么个东东.

Layouts 就是能够把 LoggingEvent转换成字符串的东东.

相比较之下, Layouts既不能控制 LoggingEvent何时输出, 也不能把 LoggingEvent聚合成批(batches). 但是 Encoder既能控制 LoggingEvent输出格式,也能控制 LoggingEvent何时输出.

不过PatternLayoutEncoder才是真正实践中使用的encoder. 它是对PatternLayout的包装.

咋说呢, encoder的抽象有点欠缺,有啥不干脆把 layout的职责也涵盖了算了?

二 Encoder interface

Encoder有2个作用:

  • 转化LoggingEvent为字节数组
  • 将字节数组通过合适的OutputStream输出
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;
}

三 LayoutWrappingEncoder

LayoutWrappingEncoder bridges(桥接) the gap between encoders and layouts. It implements the encoder interface and wraps a layout to which it delegates the work of transforming an event into string.

(注: LayoutWrappingEncoder的出现本质上是历史设计缺陷, 其实 Layout是没有必要单独拎出来的抽象, 而是完全可以作为 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);
    }
  } 
}

四 PatternLayoutEncoder

PatternLayoutEncoder是应用最为广泛 的layout,也是对 LayoutWrappingEncoder的扩展(扩展了PatternLayout职责). 从 0.9.19 版本的logback 开始, FileAppender及其子类只要配置PatternLayout就一定会使用 PatternLayoutEncoder.

五 Layouts

Layouts就是把 LoggingEvent格式化成字符串的组件(component).
我们看看接口定义:

public interface Layout<E> extends ContextAware, LifeCycle {

  String doLayout(E event);
  String getFileHeader();
  String getPresentationHeader();
  String getFileFooter();
  String getPresentationFooter();
  String getContentType();
}

在logback-classic里实际只会处理 ILoggingEvent 类型的 Event,而不会处理 AccessEvent类型的Event

六 PatternLayout

我们可以自己写一个 Layout,但坦白说, 官方给出来的 Layout已经能够满足日常需要了.往深了想, 打个日志而已, 把格式打出花来,那不也只是日志吗?

logback-classic 有一个很灵活的PatternLayout,当然它的主要职责也是拿到一个logging event,返回一个字符串.只不过这个字符串是被 PatternLayout格式化的. 格式化就是所谓的conversion pattern.

logback的 conversion pattern 和 C语言的printf 很类似.

前面已经提到过, 但 FileAppender及其子类需要一个Encoder.比如常用的ConsoleAppender就使用了PatternLayoutEncoder.

我们看个栗子:

static public void main(String[] args) throws Exception {
    Logger rootLogger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    LoggerContext loggerContext = rootLogger.getLoggerContext();
    // we are not interested in auto-configuration
    loggerContext.reset();

    PatternLayoutEncoder encoder = new PatternLayoutEncoder();
    encoder.setContext(loggerContext);
    encoder.setPattern("%-5level [%thread]: %message%n");
    encoder.start();

    ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
    appender.setContext(loggerContext);
    appender.setEncoder(encoder); 
    appender.start();

    rootLogger.addAppender(appender);

    rootLogger.debug("Message 1"); 
    rootLogger.warn("Message 2");
  }

这里的conversion pattern 就是 %-5level [%thread]: %message%n 这一坨.

七 Conversion word options

conversion specifier格式形如 %mdc{someKey}, 这个 specifier 能够获取 MDC中的K-V.
conversion specifier 还可以有多种 选项(option). 比如 conversion specifier还能够利用 自定义evluators 定制 pattern layout (当前最新版本 1.3.X 已经支持了吗?)

<pattern>%-4relative [%thread] %-5level - %msg%n   %caller{2, DISP_CALLER_EVAL, OTHER_EVAL_NAME, THIRD_EVAL_NAME}</pattern>

八 Grouping with parentheses

使用括号 () 包起来的内容叫做"grouping tokens",比如:

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

假如 "%d{HH:mm:ss.SSS} [%thread]"少于 30 各方字符就会自动填充, 这样日志看起来清爽很多,比如:

13:09:30 [main]            DEBUG c.q.logback.demo.ContextListener - Classload hashcode is 13995234
13:09:30 [main]            DEBUG c.q.logback.demo.ContextListener - Initializing for ServletContext
13:09:30 [main]            DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server
13:09:30 [pool-1-thread-1] INFO  ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 0

EventEvaluator

EventEvaluator 是个 java interface .它就是用来决定一个 LoggingEvent是否满足EventEvaluator的条件来决定是否输出这条日志的.

不过其实很少会这样用到. 假如我希望不输出这条日志,那么干啥调 logger.info这类API呢?

十 customer Converter

Converter是抽象类.
关于Converter抽象类的文档中解释是:
A minimal converter which sets up the general interface for derived classes. It also implements the functionality to chain converters in a linked list

说了等于没说.

其实Coverter的抽象很好理解,比如我希望在Pattern动态加入一些自定义的属性(变量). 看这个例子它能记录 Converter创建的时间到记录日志时间差.
步骤一:

public class MySampleConverter extends ClassicConverter {

  long start = System.nanoTime();

  @Override
  public String convert(ILoggingEvent event) {
    long nowInNanos = System.nanoTime();
    return Long.toString(nowInNanos-start);
  }
}

步骤二:

<configuration>

  <conversionRule conversionWord="nanos" 
                  converterClass="chapters.layouts.MySampleConverter" />
        
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
    <!-- 注意这里的 %6-nanos -->
b      <pattern>%-6nanos [%thread] - %msg%n</pattern>
    </encoder>
  </appender>

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

然后输出日志格式,所有日志前都带了纳秒时间:

4868695 [main] DEBUG - Everything's going well
5758748 [main] ERROR - maybe not quite...

另外,有一个MDCConverter 是比较特殊的.

十一 Logback access

11.1 PatternLayout

logback-accessPatternLayoutlogback-classic十分类似; 但是 前者会有更多的 conversion specifiers, 尤其是因为 HTTP的 request/response而定制的 conversion specifiers.
比如:

Conversion Wordfeature
a / remoteIPRemote IP address.
b / B / bytesSentResponse’s content length
h / clientHostRemote host.
H / protocolRequest protocol.
r / requestURLURL requested.
i{header} / header{header}Request header.

等等.

.

 类似资料: