Appender 概念

当一个记录日志的事件被发起时,logback 会将这个事件发送给 appender。appender 必须实现 ch.qos.logback.core.Appender 接口。这个接口的最重要的方法如下所示:

package ch.qos.logback.core;

import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.FilterAttachable;
import ch.qos.logback.core.spi.LifeCycle;


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

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

这个接口的大多数方法都是 setter 和 getter,doAppend 方式是最重要的一个。这个类接收了一个泛型类型 E,在 logback-classic 模块里, E 的类型是 ILoggingEvent,在 logback-access 模块里,E 的类型是 AccessEvent。这个接口也继承了 FilterAttachable 接口,意味着可以为这个 appender 关联一个或者多个过滤器。appender 最终会根据 layout 或者 encoder 将日志信息格式化,并且只能关联一个 layout 或者 encoder,不过有一些 appender 已经有了内嵌的 layout 或者 encoder,比如说 SocketAppender。

AppenderBase 源码分析

ch.qos.logback.core.AppenderBase 这个类是一个抽象类,是其它具体 appender 的父类。

public synchronized void doAppend(E eventObject) {

  // prevent re-entry.
  if (guard) {
    return;
  }

  try {
    guard = true;

    if (!this.started) {
      if (statusRepeatCount++ < ALLOWED_REPEATS) {
        addStatus(new WarnStatus(
            "Attempted to append to non started appender [" + name + "].",this));
      }
      return;
    }

    if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
      return;
    }

    // ok, we now invoke the derived class's implementation of append
    this.append(eventObject);

  } finally {
    guard = false;
  }
}

首先 doAppend 方法是 synchronized,保证了多线程访问这个 appender 时能起到互斥的作用,logback 也有非互斥的实现, ch.qos.logback.core.UnsynchronizedAppenderBase
guard 变量的作用是防止同一个线程内递归调用 doAppend 方法,造成死循环从而导致栈溢出。

logback-core 模块

logback-core 模块是其它模块的基础设施,以下将讨论几个 logback 自带的 appender。

OutputStreamAppender

OutputStreamAppender 将日志记录到一个 java.io.OutputStream 里,这个 appender 是其它 appender 的基础,不能直接在 logback 配置文件里面把它进行配置然后拉出来用。但这并不意味着它没有可配置的属性。

encoderEncoder日志格式
immediateFlushboolean默认值是 true,意味着日志信息直接刷到目的地,不会做缓存。如果该值设置为 false,并且没有关闭 appender,那么一部分日志信息将会丢失

OutputStreamAppender 的类继承体系如下图所示:

ConsoleAppender

ConsoleAppender 将日志信息输出到控制台,既可以是 System.out,也可以是 System.err。默认是前者。

encoderEncoder日志格式
targetString要么填System.out,要么填System.err,默认是前者。
withJansiboolean默认值是 false,主要作用是为代码着色,如果是 windows 平台,设置为 true 的同时还要在 classpath 中加入 "org.fusesource.jansi:jansi:1.9",Unix 类系统本身支持因此不用。如果想在 Eclipse 里面用,还要使用这个插件:ANSI in Eclipse Console

配置示例:

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 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="STDOUT" />
  </root>
</configuration>

FileAppender

把日志信息记录到文件里。

appendboolean如果目标文件已存在,要么追加到文件尾部,要么直接清空文件,默认是 true
encoderEncoder日志格式
fileString文件名,如果文件不存在,则重新创建,如果父目录不存在,也会重新创建,注意在 windows 下,这种写法 c:temptest.log 会有问题,因为 \t 会被转义,正确的写法是:c:/temp/test.log 或者 c:\temp\test.log
prudentboolean允许多个进程向同一个日志文件里写日志,但由于锁竞争,性能消耗大概是普通日志记录的三倍左右

默认情况下,Immediate Flush 为true,为了不丢失日志。但是也可以将其设置为 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>标签有两个必要的属性,分别是 name 和 datePattern,还有一个可选的属性 timeReference,name 所指定的可以用来作为变量使用,dataPattern 的格式与 SimpleDateFormat 的格式一样,而 timeReference 默认是配置文件被解析的时间,也可以显式指定为 contextBirth,这样时间就是上下文被创建的时间。
如下例子:

<configuration>
  <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"
             timeReference="contextBirth"/>
  ...
</configuration>

RollingFileAppender

RollingFileAppender 既可以记录日志到文件,也可以在某个条件到达后,转移到另外一个文件。有两个子组件,分别是 RollingPolicy 和 TriggeringPolicy,分别描述了如何滚动,以及何时滚动。
属性:

fileString见 FileAppender
appendboolean见 FileAppender
encoderEncoder日志格式
rollingPolicyRollingPolicy如何滚动
triggeringPolicyTriggeringPolicy何时滚动
prudentbooleanFixedWindowRollingPolicy 不支持 prudent 模式。TimeBasedRollingPolicy 支持 prudent 模式,不过有两个限制。在 prudent 模式下,无法进行文件压缩,file 属性必须留白,不能填写。

滚动策略

RollingPolicy 主要负责文件的移动和重命名

package ch.qos.logback.core.rolling;

import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.spi.LifeCycle;

public interface RollingPolicy extends LifeCycle {

  public void rollover() throws RolloverFailure;
  public String getActiveFileName();
  public CompressionMode getCompressionMode();
  public void setParent(FileAppender appender);
}

TimeBasedRollingPolicy

这个策略基于时间,比如说基于 day 或者 month 的滚动,同时实现了 RollingPolicy 接口和 TriggeringPolicy 接口,既定义了如何滚动,也定义了何时滚动。
TimeBasedRollingPolicy 只有一个必须的属性 fileNamePattern 和几个可选的属性。

fileNamePattern 包含实际的文件名,以及一个 %d 标识,这个标识的用法是 %d{},其中大括号里面是遵循 java.text.SimpleDataFormat 格式的日期模式,logback 会根据这个日期模式的最小单位来推断出是根据天、月、年还是分钟、小时来进行滚动的。
RollingFileAppender 有一个 file 属性,在配合 TimeBasedRollingPolicy 使用的时候,这个 file 的属性是否被设置,有着不同的区别。如果 file 属性设置了,那么每次新记录的日志将会记录到这个文件,当滚动时需要重命名的时候,则将这个文件名以 fileNamePattern 指定的模式进行重命名和归档。如果 file 属性没有进行设置,那么每次新纪录的日志会记录到根据 fileNamePattern 指定的当前日志文件里。
如果有多个 %d 标识,那么必须有一个是 primary 的,其它的为 auxiliary 的。例如 /var/log/%d{yyyy/MM, aux}/myapplication.%d{yyyy-MM-dd}.log 。同时,fileNamePattern 支持填写 Time Zone,例如 aFolder/test.%d{yyyy-MM-dd-HH, UTC}.log

maxHistory 属性指定要保留多长时间的日志,依据从 fileNamePattern 中推断出来的单位。

totalSizeGap 属性限制了日志文件的大小总共不能超过的大小。

cleanHistoryOnStart 指定了是否重启应用的时候删除之前的归档日志。

以下是一些常见的 fileNamePattern,以及它们的作用。

/wombat/foo.%dDaily rollover (at midnight). Due to the omission of the optional time and date pattern for the %d token specifier, the default pattern of yyyy-MM-dd is assumed, which corresponds to daily rollover.If the file property not set: During November 23rd, 2006, logging output will go to the file /wombat/foo.2006-11-23. At midnight and for the rest of the 24th, logging output will be directed to /wombat/foo.2006-11-24. If the file property set to /wombat/foo.txt: During November 23rd, 2006, logging output will go to the file /wombat/foo.txt. At midnight, foo.txt will be renamed as /wombat/foo.2006-11-23. A new /wombat/foo.txt file will be created and for the rest of November 24th logging output will be directed to foo.txt.
/wombat/%d{yyyy/MM}/foo.txtRollover at the beginning of each month.If the file property not set:During the month of October 2006, logging output will go to /wombat/2006/10/foo.txt. After midnight of October 31st and for the rest of November, logging output will be directed to /wombat/2006/11/foo.txt. If the file property set to /wombat/foo.txt:The active log file will always be /wombat/foo.txt. During the month of October 2006, logging output will go to /wombat/foo.txt. At midnight of October 31st, /wombat/foo.txt will be renamed as /wombat/2006/10/foo.txt. A new /wombat/foo.txt file will be created where logging output will go for the rest of November. At midnight of November 30th, /wombat/foo.txt will be renamed as /wombat/2006/11/foo.txt and so on.
/wombat/foo.%d{yyyy-ww}.logRollover at the first day of each week. Note that the first day of the week depends on the locale.Similar to previous cases, except that rollover will occur at the beginning of every new week.
/wombat/foo%d{yyyy-MM-dd_HH}.logRollover at the top of each hour.Similar to previous cases, except that rollover will occur at the top of every hour.
/wombat/foo%d{yyyy-MM-dd_HH-mm}.logRollover at the beginning of every minute.Similar to previous cases, except that rollover will occur at the beginning of every minute.
/wombat/foo%d{yyyy-MM-dd_HH-mm, UTC}.logRollover at the beginning of every minute.Similar to previous cases, except that file names will be expressed in UTC.
/foo/%d{yyyy-MM,aux}/%d.logRollover daily. Archives located under a folder containing year and month.In this example, the first %d token is marked as auxiliary. The second %d token, with time and date pattern omitted, is then assumed to be primary. Thus, rollover will occur daily (default for %d) and the folder name will depend on the year and month. For example, during the month of November 2006, archived files will all placed under the /foo/2006-11/ folder, e.g /foo/2006-11/2006-11-14.log.
/wombat/foo.%d.gzDaily rollover (at midnight) with automatic GZIP compression of the archived files. If the file property not set: During November 23rd, 2009, logging output will go to the file /wombat/foo.2009-11-23. However, at midnight that file will be compressed to become /wombat/foo.2009-11-23.gz. For the 24th of November, logging output will be directed to /wombat/folder/foo.2009-11-24 until it's rolled over at the beginning of the next day. If the file property set to /wombat/foo.txt: During November 23rd, 2009, logging output will go to the file /wombat/foo.txt. At midnight that file will be compressed and renamed as /wombat/foo.2009-11-23.gz. A new /wombat/foo.txt file will be created where logging output will go for the rest of November 24rd. At midnight November 24th, /wombat/foo.txt will be compressed and renamed as /wombat/foo.2009-11-24.gz and so on.

配置示例:

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logFile.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- daily rollover -->
      <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>

      <!-- keep 30 days' worth of history capped at 3GB total size -->
      <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

这个策略除了具有 TimeBasedRollingPolicy 的功能外,还能限制单个日志文件的大小,当单个日志到达指定的大小时,触发日志滚动。

<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>
       <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 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>

通过 maxFileSize 来设置单个日志文件的大小限制。注意到这里,有一个新的标识出现 %i,这是当发生滚动时,可以为同一天的日志加上不同的后缀 index。

FixedWindowRollingPolicy

配置示例:

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>test.log</file>

    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
      <fileNamePattern>foo%i.log</fileNamePattern>
      <minIndex>1</minIndex>
      <maxIndex>3</maxIndex>
    </rollingPolicy>

    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>5MB</maxFileSize>
    </triggeringPolicy>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>

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

以一个例子来认识 FixedWindowRollingPolicy

0foo.log-No rollover has happened yet, logback logs into the initial file.
1foo.logfoo1.logFirst rollover. foo.log is renamed as foo1.log. A new foo.log file is created and becomes the active output target.
2foo.logfoo1.log,foo2.logSecond rollover. foo1.log is renamed as foo2.log. foo.log is renamed as foo1.log. A new foo.log file is created and becomes the active output target.
3foo.logfoo1.log, foo2.log, foo3.logThird rollover. foo2.log is renamed as foo3.log. foo1.log is renamed as foo2.log. foo.log is renamed as foo1.log. A new foo.log file is created and becomes the active output target.
4foo.logfoo1.log, foo2.log, foo3.logIn this and subsequent rounds, the rollover begins by deleting foo3.log. Other files are renamed by incrementing their index as shown in previous steps. In this and subsequent rollovers, there will be three archive logs and one active log file.

SizeBasedTriggeringPolicy

配置示例,一般配合 FixedWindowRollingPolicy:

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>test.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
      <fileNamePattern>test.%i.log.zip</fileNamePattern>
      <minIndex>1</minIndex>
      <maxIndex>3</maxIndex>
    </rollingPolicy>

    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>5MB</maxFileSize>
    </triggeringPolicy>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>

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

03-05 23:18