Skip to content

Logback 手册 - 第四章:Appenders

🏷️ Logback 手册


来源:https://logback.qos.ch/manual/appenders.html
作者:Ceki Gülcü、Sébastien Pennec、Carl Harris
版权所有 © 2000-2022 QOS.ch Sarl

本文档使用 知识共享署名 - 非商业性使用 - 相同方式共享 2.5 许可协议 授权。


There is so much to tell about the Western country in that day that it is hard to know where to start. One thing sets off a hundred others. The problem is to decide which one to tell first.

有关这个西方国家的许多事情都值得讲述,但是从何说起又很难决定。一个问题引出一百个问题。问题在于决定先说哪一个。

——约翰·斯坦贝克,《伊甸园之东》


第 4 章:Appenders

WARNING

为了运行本章中的示例,您需要确保某些 JAR 文件在类路径上存在。请参考 安装页面 以获取更多详细信息。

什么是 Appender?

Logback 将写入日志事件的任务委托给称为 appender 的组件。Appender 必须实现 ch.qos.logback.core.Appender 接口。该接口的主要方法如下所述:

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

}

Appender 接口中的大多数方法都是设置器和获取器。一个值得注意的例外是 doAppend() 方法,它以类型为 E 的对象实例作为其唯一参数。E 的实际类型将根据 logback 模块的不同而变化。在 logback-classic 模块中,E 将是类型为 ILoggingEvent 的对象,而在 logback-access 模块中,E 将是类型为 AccessEvent 的对象。doAppend() 方法可能是 logback 框架中最重要的方法。它负责以适当的格式将日志事件输出到相应的输出设备。

Appender 是有名称的实体。这确保它们可以通过名称引用,这是配置脚本中的一种确认质量。Appender 接口扩展了 FilterAttachable 接口。因此,一个或多个过滤器可以附加到 appender 实例上。过滤器将在后续章节中详细讨论。

Appender 最终负责输出日志事件。然而,它们可能会将事件的实际格式化委托给 LayoutEncoder 对象。每个布局/编码器与一个且仅一个 appender 相关联,称为所属 appender。一些 appender 具有内置或固定的事件格式。因此,它们不需要也没有布局/编码器。例如,SocketAppender 只是在传输日志事件之前对其进行序列化。

AppenderBase

ch.qos.logback.core.AppenderBase 类是实现 Appender 接口的抽象类。它提供了所有 appender 都共享的基本功能,例如获取或设置其名称、激活状态、布局和过滤器的方法。它是 logback 随附的所有 appender 的超类。虽然是一个抽象类,但 AppenderBase 实际上在 Append 接口中实现了 doAppend() 方法。讨论 AppenderBase 类最清晰的方式可能是通过展示实际源代码的摘录。

java
public synchronized void doAppend(E eventObject) {

  // 防止重入。
  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;
    }

    // 好的,现在我们调用派生类的 append 实现
    this.append(eventObject);

  } finally {
    guard = false;
  }
}

doAppend() 方法的实现是同步的。由此可见,从不同线程记录到相同的 appender 是安全的。当一个线程,比如 T,正在执行 doAppend() 方法时,其他线程的后续调用会被排队,直到 T 离开 doAppend() 方法,确保 T 独占 appender。

由于这种同步并不总是适当的,logback 附带了 ch.qos.logback.core.UnsynchronizedAppenderBase,它与 AppenderBase 类非常相似。为了简洁起见,我们在本文档的其余部分将讨论 UnsynchronizedAppenderBase

doAppend() 方法的第一件事是检查守卫是否设置为 true。如果是,它立即退出。如果守卫未设置,则在下一条语句中设置为 true。守卫确保 doAppend() 方法不会递归调用自身。想象一下,在 append() 方法之外的某个地方调用了一个组件,想要记录一些东西。它的调用可能被定向到刚刚调用它的同一个 appender,导致无限循环和堆栈溢出。

在接下来的语句中,我们检查 started 字段是否为 true。如果不是,doAppend() 将发出警告消息并返回。换句话说,一旦 appender 被关闭,就不可能向其写入数据。Appender 对象实现了 LifeCycle 接口,这意味着它们实现了 start()stop()isStarted() 方法。在设置完 appender 的所有属性之后,logback 的配置框架 Joran 调用 start() 方法,以通知 appender 激活其属性。根据其类型,如果缺少某些属性或因各种属性之间的干扰而无法启动,某个 appender 可能无法启动。例如,考虑到文件创建取决于截断模式,FileAppender 在不确定 Append 选项的值之前无法对其 File 选项的值进行操作。显式的激活步骤确保 appender 在其值变为已知之后再对其属性进行操作。

如果 appender 无法启动或已停止,doAppend() 方法将通过 logback 的内部状态管理系统发出警告消息。经过多次尝试,为了避免使内部状态系统充斥相同警告消息的副本,doAppend() 方法将停止发出这些警告。

接下来的 if 语句检查了附加过滤器的结果。根据过滤器链的决定,事件可以被拒绝或显式接受。如果过滤器链没有做出决定,事件将默认被接受。

doAppend() 方法然后调用派生类的 append() 方法的实现。这个方法实际上是将事件附加到适当的设备上的工作。

最后,守卫被释放,以允许后续调用 doAppend() 方法。

在本手册的其余部分,我们将保留术语 "option" 或者 "property",用于指代通过 JavaBeans 内省动态推断出的任何属性,通过 setter 和 getter 方法。

Logback-core

Logback-core 为其他 logback 模块构建打下了基础。一般来说,logback-core 中的组件需要进行一些,尽管很少量的自定义。然而,在接下来的几节中,我们将描述几个可以直接使用的 appender。

OutputStreamAppender

OutputStreamAppender 将事件追加到 java.io.OutputStream。这个类提供了其他 appender 基于的基本服务。通常情况下,用户不会直接实例化 OutputStreamAppender 对象,因为一般情况下 java.io.OutputStream 类型无法方便地映射到字符串,因为没有办法在配置脚本中指定目标 OutputStream 对象。简而言之,你无法从配置文件配置 OutputStreamAppender。然而,这并不意味着 OutputStreamAppender 缺少可配置的属性。下面描述了这些属性。

属性名称类型描述
encoderEncoder决定事件被写入底层 OutputStreamAppender 的方式。编码器在 专门章节 中描述。
immediateFlushbooleanimmediateFlush 的默认值为 'true'。立即刷新输出流可确保日志事件立即被写出,以防止应用程序在不正确关闭 appender 的情况下退出而丢失日志事件。另一方面,将此属性设置为'false'可能会使日志吞吐量增加四倍(结果可能有所不同)。同样,如果 immediateFlush 设置为'false',并且在应用程序退出时未正确关闭 appender,那么尚未写入磁盘的日志事件可能会丢失。

OutputStreamAppender 是其他三个 appender 的超类,分别是 ConsoleAppenderFileAppender,后者又是 RollingFileAppender 的超类。下图显示了 OutputStreamAppender 及其子类的类图。

显示 OutputStreamAppender 和子类的 UML 图

ConsoleAppender

ConsoleAppender,顾名思义,将事件追加到控制台,更确切地说是 System.outSystem.err,前者是默认目标。ConsoleAppender 使用用户指定的编码器格式化事件。编码器将在后续章节中讨论。System.outSystem.err 都是 java.io.PrintStream 类型。因此,它们被包装在一个缓冲 I/O 操作的 OutputStreamWriter 中。

属性名称类型描述
encoderEncoder参见 OutputStreamAppender 的属性。
targetString字符串值之一为 System.outSystem.err。默认目标是 System.out
withJansiboolean默认情况下 withJansi 属性被设置为 false。将 withJansi 设置为 true 会激活 Java 库,在 Windows 机器上提供对 ANSI 颜色代码的支持。在 Windows 主机上,如果此属性设置为 true,则应将 "org.fusesource.jansi:jansi:${jansi.version}" 放在类路径上。请注意,类 Unix 操作系统(如 Linux 和 Mac OS X)默认支持 ANSI 颜色代码。

在 Eclipse IDE 下,您可能想尝试 ANSI in Eclipse Console 插件。

以下是一个使用 ConsoleAppender 的示例配置。

示例:ConsoleAppender 配置 (logback-examples/src/main/resources/chapters/appenders/conf/logback-Console.xml)

在将当前路径设置为 logback-examples 目录并 设置类路径 后,您可以通过发出以下命令来测试上述配置文件:

java chapters.appenders.ConfigurationTester src/main/java/chapters/appenders/conf/logback-Console.xml

文件追加器(FileAppender)

FileAppenderOutputStreamAppender 的子类,用于将日志事件追加到文件中。目标文件由 File 选项指定。如果文件已经存在,则根据追加属性的值进行追加或截断。

属性名称类型描述
appendboolean如果为 true,则事件追加到现有文件的末尾。否则,如果 appendfalse,则会截断任何现有文件。默认情况下,追加选项设置为 true
encoderEncoder参见 OutputStreamAppender 属性。
fileString要写入的文件的名称。如果文件不存在,将创建它。在 MS Windows 平台上,用户经常忘记转义反斜杠。例如,值"c:\\temp\\test.log" 可能无法正确解释为 "'\\t'" 是一个转义序列,被解释为单个制表符字符(\u0009)。可以将正确的值指定为 "c:/temp/test.log",或者作为 "c:\\\\temp\\\\test.log"file 选项没有默认值。

如果文件的父目录不存在,FileAppender 将自动创建它,包括任何必要但不存在的父目录。
bufferSizeFileSizebufferSize 选项在 immediateFlush 选项设置为 false 的情况下设置输出缓冲区的大小。bufferSize 的默认值为 8192。即使在非常重载和持久的负载情况下,256KKB 的值似乎也足够。

以“FileSize”为单位定义的选项可以通过将数字值与 KB、MB 和 GB 后缀一起指定为字节、千字节、兆字节或千兆字节。例如,5000000、5000KB、5MB 和 2GB 都是有效的值,前三个值相等。
prudentboolean在谨慎模式下,FileAppender 将安全地写入指定的文件,即使在不同 JVM 中运行的其他 FileAppender 实例存在的情况下,这些实例可能在不同的主机上运行。谨慎模式的默认值为 false

谨慎模式可以与 RollingFileAppender 结合使用,但是存在一些 限制

谨慎模式意味着 append 属性会自动设置为 true

谨慎模式依赖于独占文件锁。实验证明,文件锁大约会使写入日志事件的成本增加三倍(x3)。在一个“平均”的个人电脑上,当谨慎模式关闭时,写入单个日志事件大约需要 10 微秒。当打开谨慎模式时,输出单个日志事件大约需要 30 微秒。这意味着在谨慎模式关闭时,每秒的日志吞吐量为 100,000 个事件,在谨慎模式下约为 33,000 个事件。

谨慎模式有效地对所有写入同一文件的 JVM 之间的 I/O 操作进行串行化。因此,随着竞争访问文件的 JVM 数量的增加,每个 I/O 操作所需的延迟也会增加。只要“总共”I/O 操作的数量约为每秒 20 个日志请求,对性能的影响应该可以忽略不计。生成每秒 100 个或更多 I/O 操作的应用程序可能会对性能产生影响,应避免使用谨慎模式。

网络文件锁 当日志文件位于网络文件系统上时,谨慎模式的成本甚至更高。同样重要的是,网络文件系统上的文件锁有时可能严重偏向于当前拥有锁的进程在释放锁后立即重新获取锁。因此,当一个进程占用日志文件的锁时,其他进程会因等待锁而饿死,达到看起来死锁的程度。

谨慎模式的影响在很大程度上取决于网络速度以及操作系统的实现细节。我们提供了一个非常小的应用程序,名为 FileLockSimulator,它可以帮助您模拟在您的环境中使用谨慎模式的行为。

立即刷新

默认情况下,每个日志事件都会立即刷新到底层输出流。这种默认方法更安全,因为在应用程序在没有正确关闭追加器的情况下退出时,日志事件不会丢失。然而,为了显著增加日志吞吐量,可以将 immediateFlush 属性设置为 false

以下是 FileAppender 的配置文件示例:

示例:FileAppender 配置(logback-examples/src/main/resources/chapters/appenders/conf/logback-fileAppender.xml)

使用以下命令尝试一下:

bash
java -Dfrom=source@xyz.com -Dto=recipient@xyz.com -DsmtpHost=some_smtp_host \
  chapters.appenders.mail.Marked_EMail src/main/java/chapters/appenders/mail/mailWithMarker.xml

基于标记的触发与 JaninoEventEvaluator

请注意,我们可以使用更通用的 JaninoEventEvaluator 代替基于标记的 OnMarkerEvaluator。例如,下面的配置文件使用 JaninoEventEvaluator 而不是 OnMarkerEvaluator,但与先前的配置文件相同。

示例:带有 JaninoEventEvaluatorSMTPAppender(logback-examples/src/main/resources/chapters/appenders/mail/mailWithMarker_Janino.xml)

传统风格

xml
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
      <expression>
        (marker != null) &&
        (marker.contains("NOTIFY_ADMIN") || marker.contains("TRANSACTION_FAILURE"))
      </expression>
    </evaluator>
    <smtpHost>${smtpHost}</smtpHost>
    <to>${to}</to>
    <from>${from}</from>
    <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
  </appender>
</configuration>

规范化(1.3 版本)

xml
无法转换

身份验证/STARTTLS/SSL

SMTPAppender 支持使用明文用户密码以及 STARTTLS 和 SSL 协议进行身份验证。请注意,STARTTLS 与 SSL 的不同之处在于,在 STARTTLS 中,连接最初是非加密的,只有在客户端发出 STARTTLS 命令(如果服务器支持)后,连接才会切换到 SSL。在 SSL 模式下,连接从一开始就是加密的。

用于 Gmail 的 SMTPAppender 配置(SSL)

下面的示例展示了如何使用 SSL 协议配置 SMTPAppender 以将日志发送到 Gmail。

示例:使用 SSL 将日志发送到 Gmail 的 SMTPAppender(logback-examples/src/main/resources/chapters/appenders/mail/gmailSSL.xml)

传统风格

xml
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>465</smtpPort>
    <SSL>true</SSL>
    <username>YOUR_USERNAME@gmail.com</username>
    <password>YOUR_GMAIL_PASSWORD</password>

    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to> <!-- 可以添加额外的目标邮箱 -->
    <from>YOUR_USERNAME@gmail.com</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>%date %-5level %logger{35} - %message%n</pattern>
    </layout>
  </appender>

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

规范化(1.3 版本)

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>

<configuration>
  <import class="ch.qos.logback.classic.net.SMTPAppender"/>
  <import class="ch.qos.logback.classic.PatternLayout"/>

  <appender name="EMAIL" class="SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>465</smtpPort>
    <sSL>true</sSL>
    <username>YOUR_USERNAME@gmail.com</username>
    <password>YOUR_GMAIL_PASSWORD</password>
    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to>
    <from>YOUR_USERNAME@gmail.com</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <layout class="PatternLayout">
      <pattern>%date %-5level %logger{35} - %message%n</pattern>
    </layout>
  </appender>

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

用于 Gmail 的 SMTPAppender(STARTTLS)

下面的示例展示了如何为 STARTTLS 协议配置 SMTPAppender,以使用 Gmail。

示例:使用 STARTTLS 将 SMTPAppender 配置为 Gmail(logback-examples/src/main/resources/chapters/appenders/mail/gmailSTARTTLS.xml)

旧版配置

xml
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>587</smtpPort>
    <STARTTLS>true</STARTTLS>
    <username>YOUR_USERNAME@gmail.com</username>
    <password>YOUR_GMAIL_xPASSWORD</password>

    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to> <!-- 可以添加其他目标地址 -->
    <from>YOUR_USERNAME@gmail.com</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>%date %-5level %logger - %message%n</pattern>
    </layout>
  </appender>

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

规范配置(1.3 版)

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>

<configuration>
  <import class="ch.qos.logback.classic.net.SMTPAppender"/>
  <import class="ch.qos.logback.classic.PatternLayout"/>

  <appender name="EMAIL" class="SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>587</smtpPort>
    <sTARTTLS>true</sTARTTLS>
    <username>YOUR_USERNAME@gmail.com</username>
    <password>YOUR_GMAIL_xPASSWORD</password>
    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to>
    <from>YOUR_USERNAME@gmail.com</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <layout class="PatternLayout">
      <pattern>%date %-5level %logger - %message%n</pattern>
    </layout>
  </appender>

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

带有 MDCDiscriminator 的 SMTPAppender

如前所述,通过指定除默认分配器以外的分配器,SMTPAppender 将生成包含与特定用户、用户会话或客户端 IP 地址相关的事件的电子邮件消息,具体取决于指定的分配器。

下面的示例演示了在 MDC 键为 "req.remoteHost" 的情况下使用 MDCBasedDiscriminator 以及假设该键包含访问虚构应用程序的远程主机的 IP 地址。在 Web 应用程序中,您可以使用 MDCInsertingServletFilter 来填充 MDC 值。

示例:带有 MDCBasedDiscriminatorSMTPAppender(logback-examples/src/main/resources/chapters/appenders/mail/mailWithMDCBasedDiscriminator.xml)

旧版配置

xml
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>ADDRESS-OF-YOUR-SMTP-HOST</smtpHost>
    <to>EMAIL-DESTINATION</to>
    <from>SENDER-EMAIL</from>

    <discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator">
      <key>req.remoteHost</key>
      <defaultValue>default</defaultValue>
    </discriminator>

    <subject>${HOSTNAME} -- %X{req.remoteHost} %msg"</subject>
    <layout class="ch.qos.logback.classic.html.HTMLLayout">
      <pattern>%date%level%thread%X{req.remoteHost}%X{req.requestURL}%logger%msg</pattern>
    </layout>
  </appender>

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

规范配置(1.3 版)

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>

<configuration>
  <import class="ch.qos.logback.classic.html.HTMLLayout"/>
  <import class="ch.qos.logback.classic.net.SMTPAppender"/>
  <import class="ch.qos.logback.classic.sift.MDCBasedDiscriminator"/>

  <appender name="EMAIL" class="SMTPAppender">
    <smtpHost>ADDRESS-OF-YOUR-SMTP-HOST</smtpHost>
    <to>EMAIL-DESTINATION</to>
    <from>SENDER-EMAIL</from>
    <discriminator class="MDCBasedDiscriminator">
      <key>req.remoteHost</key>
      <defaultValue>default</defaultValue>
    </discriminator>
    <subject>${HOSTNAME} -- %X{req.remoteHost} %msg"</subject>
    <layout class="HTMLLayout">
      <pattern>%date%level%thread%X{req.remoteHost}%X{req.requestURL}%logger%msg</pattern>
    </layout>
  </appender>

  <root>
    <appender-ref ref="EMAIL"/>
  </root>
</configuration>

因此,由 SMTPAppender 生成的每封外发邮件都属于一个 唯一 的远程主机,极大地方便了问题诊断。

非常繁忙的系统中的缓冲区管理

内部,每个鉴别器返回的不同值将导致创建一个新的循环缓冲区。然而,最多会维护 maxNumberOfBuffers(默认为 64)个缓冲区。每当缓冲区数量上升超过 maxNumberOfBuffers 时,最近更新的缓冲区将自动丢弃。作为第二个安全措施,任何在过去 30 分钟内未更新的缓冲区也将自动丢弃。

在每分钟服务大量交易的系统中,允许 maxNumberOfBuffers(默认为 64)的数量往往会导致发送邮件中的事件数量不必要地小。事实上,在存在大量交易的情况下,将有多个与同一交易相关联的缓冲区,因为缓冲区将为相同的鉴别器值(或交易)连续被删除和重建。请注意,即使是这样非常繁忙的系统,循环缓冲区的最大数量也受到 maxNumberOfBuffers 的限制。

为了避免这种类似于哑铃的效应,SMTPAppender 将在看到标记为 "FINALIZE_SESSION" 的事件时释放与给定鉴别器键相关联的缓冲区。这将导致在每个交易结束时丢弃相应的缓冲区。您可以放心地将 maxNumberOfBuffers 的值增加到更大的值,例如 512 或 1024,而不必担心内存耗尽。

有三种不同但相互补充的机制一起管理循环缓冲区。它们确保任何特定时刻只保留相关的缓冲区,即使在非常繁忙的系统中也是如此。

DBAppender

DBAppender 会以与 Java 编程语言无关的格式将日志事件插入到三个数据库表中。

截至 logback 版本 1.2.8,DBAppender 不再随 logback-classic 一起提供。但是,logback-classic 的 DBAppender 可在以下 Maven 坐标下使用:

ch.qos.logback.db:logback-classic-db:1.2.11.1

这三张表分别是 logging_eventlogging_event_propertylogging_event_exception。在使用 DBAppender 之前,它们必须存在。Logback 附带了 SQL 脚本来创建这些表。它们可以在 logback-classic/src/main/java/ch/qos/logback/classic/db/script 文件夹下找到。每种流行的数据库系统都有一个特定的脚本。如果您使用的数据库系统的脚本丢失,则应该很容易编写一个,以现有脚本为例。如果您将它们发送给我们,我们将很乐意在未来的版本中包含缺少的脚本。

如果您的 JDBC 驱动程序支持 JDBC 3.0 规范中引入的 getGeneratedKeys 方法,并假定您已经创建了上述适当的数据库表,则不需要采取任何其他步骤。否则,必须有适用于您的数据库系统的 SQLDialect。目前,logback 具有适用于 H2、HSQL、MS SQL Server、MySQL、Oracle、PostgreSQL、SQLLite 和 Sybase 的方言。

下表总结了数据库类型及其对 getGeneratedKeys() 方法的支持。

RDBMS测试版本测试的 JDBC 驱动版本支持 getGeneratedKeys() 方法logback 是否提供方言
DB2未测试未测试未知
H21.2.132-未知
HSQL1.8.0.7-
Microsoft SQL Server20052.0.1008.2 (sqljdbc.jar)
MySQL5.0.225.0.8 (mysql-connector.jar)
PostgreSQL8.x8.4-701.jdbc4
Oracle10g10.2.0.1 (ojdbc14.jar)
SQLLite3.7.4-未知
Sybase SQLAnywhere10.0.1-未知

实验表明,在“标准”个人电脑上,将单个事件写入数据库大约需要 10 毫秒。如果使用池化连接,则此数字会降至约 1 毫秒。请注意,大多数 JDBC 驱动程序已经提供了连接池支持。

配置 logback 以使用 DBAppender 可以通过几种不同的方式完成,具体取决于用于连接到数据库的工具和数据库本身。配置 DBAppender 的关键问题是设置其 ConnectionSource 对象,我们很快就会发现。

一旦为您的数据库配置了 DBAppender,日志事件就会被发送到指定的数据库。如前所述,logback 使用三个表存储日志事件数据。

logging_event 表包含以下字段:

字段类型描述
timestampbig int日志事件创建时有效的时间戳。
formatted_messagetext在将对象与消息一起传递后,使用 org.slf4j.impl.MessageFormatter 格式化后添加到日志事件中的消息。
logger_namevarchar发出日志请求的记录器的名称。
level_stringvarchar日志事件的级别。
reference_flagsmallint此字段由 logback 用于标识具有异常或 MDC 属性值的日志事件。

它的值由 ch.qos.logback.classic.db.DBHelper 计算。包含 MDCContext 属性的日志事件具有标志数为 1。包含异常的日志事件具有标志数为 2。同时包含两个元素的日志事件具有标志数为 3
caller_filenamevarchar发出日志请求的文件的名称。
caller_classvarchar发出日志请求的类。
caller_methodvarchar发出日志请求的方法的名称。
caller_linechar发出日志请求的行号。
event_idint日志事件的数据库 ID。

logging_event_property 用于存储包含在 MDCContext 中的键和值。它包含以下字段:

字段类型描述
event_idint日志事件的数据库 ID。
mapped_keyvarcharMDC 属性的键
mapped_valuetextMDC 属性的值

logging_event_exception 表包含以下字段:

字段类型描述
event_idint日志事件的数据库 ID。
ismallint完整堆栈跟踪中的行索引。
trace_linevarchar相应的行

为了更直观地展示 DBAppender 的工作,下面是一个由 DBAppender 提供内容的 MySQL 数据库的屏幕截图。

logging_event 表:

Logging Event table

logging_event_exception 表:

Logging Event Exception table

logging_event_property 表:

Logging Event Property table

ConnectionSource

ConnectionSource 接口提供了一种可插拔的方式,用于透明地为需要使用 java.sql.Connection 的 logback 类获取 JDBC 连接。目前有三种 ConnectionSource 的实现,分别是 DataSourceConnectionSourceDriverManagerConnectionSourceJNDIConnectionSource

我们将首先介绍使用 DriverManagerConnectionSource 和 MySQL 数据库的配置示例。下面是一个配置文件示例。

示例:DBAppender 配置 (logback-examples/src/main/resources/chapters/appenders/db/append-toMySQL-with-driverManager.xml)

必须声明正确的驱动程序。这里使用的是 com.mysql.jdbc.Driver 类。URL 必须以 jdbc:mysql:// 开头。

DriverManagerConnectionSourceConnectionSource 的一个实现,它根据连接 URL 以传统的 JDBC 方式获取连接。

请注意,该类将为每次调用 getConnection() 建立一个新的 Connection。建议您要么使用本地支持连接池的 JDBC 驱动程序,要么创建自己的 ConnectionSource 实现,以利用您已经使用的任何池化机制。如果您可以访问支持 javax.sql.DataSource 的 JNDI 实现(例如在 J2EE 应用服务器中),请参阅下面的 JNDIConnectionSource

使用 DataSource 连接到数据库相当类似。现在的配置使用了 DataSourceConnectionSource,它是一个基于 javax.sql.DataSource 的推荐 JDBC 方式来获取 ConnectionConnectionSource 的实现。

示例:DBAppender 配置 (logback-examples/src/main/resources/chapters/appenders/db/append-with-datasource.xml)

请注意,在此配置示例中,我们大量使用了替代变量。当连接详细信息必须集中在单个配置文件中,并由 logback 和其他框架共享时,它们有时非常方便。

JNDIConnectionSource

JNDIConnectionSource 是 logback 中提供的另一个 ConnectionSource 实现。正如其名称所示,它从 JNDI 中检索一个 javax.sql.DataSource,然后利用它来获取一个 java.sql.Connection 实例。JNDIConnectionSource 主要设计用于在 J2EE 应用服务器内部或应用服务器客户端中使用,假设应用服务器支持远程访问 javax.sql.DataSource。因此,可以利用连接池和应用服务器提供的其他好处。更重要的是,您的应用程序将变得更加干净,因为在 logback.xml 中不再需要定义 DataSource

例如,这里是针对 Tomcat 的配置片段。虽然任何受支持的数据库系统(如上面列出的)都可以工作,但它假定 PostgreSQL 作为数据库。

xml
<Context docBase="/path/to/app.war" path="/myapp">
  ...
  <Resource name="jdbc/logging"
               auth="Container"
               type="javax.sql.DataSource"
               username="..."
               password="..."
               driverClassName="org.postgresql.Driver"
               url="jdbc:postgresql://localhost/..."
               maxActive="8"
               maxIdle="4"/>
  ...
</Context>

一旦在 J2EE 服务器中定义了一个 DataSource,就可以在您的 logback 配置文件中轻松引用它,如下例所示。

示例:通过 JNDIConnectionSource 配置 DBAppender(logback-examples/src/main/resources/chapters/appenders/db/append-via-jndi.xml)

请注意,该类将使用无参数构造函数获取一个 javax.naming.InitialContext。当在 J2EE 环境中执行时,这通常会起作用。当不在 J2EE 环境中时,请确保按照您的 JNDI 提供程序文档的描述提供一个 jndi.properties 文件。

连接池

日志事件可能会以相当快的速度创建。为了跟上必须插入数据库的事件流,建议使用 DBAppender 进行连接池操作。

实验证明,使用连接池与 DBAppender 可以大大提高性能。通过以下配置文件,日志事件被发送到一个 MySQL 数据库,且没有进行任何连接池操作。

示例:没有连接池的 DBAppender 配置(logback-examples/src/main/resources/chapters/appenders/db/append-toMySQL-with-datasource.xml)

通过这个配置文件,向 MySQL 数据库发送 500 个日志事件需要长达 5 秒,即每个请求需要 10 毫秒。这个数字在处理大型应用程序时是不可接受的。

使用 DBAppender 与连接池需要一个专用的外部库。以下示例使用 c3p0。为了能够使用 c3p0,必须下载它并将 c3p0-VERSION.jar 放置在类路径中。

示例:带有连接池的 DBAppender 配置(logback-examples/src/main/resources/chapters/appenders/db/append-toMySQL-with-datasource-and-pooling.xml)

通过这个新的配置,向上述的 MySQL 数据库发送 500 个日志请求大约需要 0.5 秒,平均每个请求 1 毫秒,性能提高了十倍。

SyslogAppender

syslog 协议是一个非常简单的协议:syslog 发送方将一个小消息发送到 syslog 接收方。接收方通常称为 syslog 守护进程syslog 服务器。Logback 可以向远程 syslog 守护进程发送消息,使用 SyslogAppender 实现。

下面是可以传递给 SyslogAppender 的属性。

属性名称类型描述
syslogHostStringsyslog 服务器的主机名。
portString要连接的 syslog 服务器上的端口号。通常情况下,不希望更改默认值 514
facilityStringfacility 用于标识消息的来源。

facility 选项必须设置为以下字符串之一:KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, NTP, AUDIT, ALERT, CLOCK, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7。大小写不重要。
suffixPatternStringsuffixPattern 选项指定发送到 syslog 服务器的消息中非标准化部分的格式。默认情况下,它的值为 [%thread] %logger %msg。任何 PatternLayout 可以使用的值都是正确的 suffixPattern 值。
stackTracePatternStringstackTracePattern 属性允许自定义出现在每个堆栈跟踪行之前的字符串。此属性的默认值是 "\t",即制表符字符。任何被 PatternLayout 接受的值都是 stackTracePattern 的有效值。
throwableExcludedbooleanthrowableExcluded 设置为 true 将导致省略与 Throwable 相关联的堆栈跟踪数据。默认情况下,throwableExcluded 设置为 false,以便将堆栈跟踪数据发送到 syslog 服务器。

日志事件的 syslog 严重程度从日志事件的级别转换而来。DEBUG 级别转换为 7INFO 转换为 6WARN 转换为 4ERROR 转换为 3

由于 syslog 请求的格式遵循相当严格的规则,因此没有布局可与 SyslogAppender 一起使用。但是,使用 suffixPattern 选项可以让用户显示他想要的任何信息。

下面是使用 SyslogAppender 的示例配置。

示例:SyslogAppender 配置(logback-examples/src/main/resources/chapters/appenders/conf/logback-syslog.xml)

在测试此配置时,应验证远程 syslog 守护进程是否接受来自外部源的请求。经验表明,默认情况下,syslog 守护进程通常会拒绝通过网络连接发送的请求。

SiftingAppender

正如其名,SiftingAppender 可以根据给定的运行时属性将日志分离(或筛选)。例如,SiftingAppender 可以根据用户会话将日志事件分离,使不同用户生成的日志进入不同的日志文件,每个用户一个日志文件。

属性名称类型描述
timeoutDuration超过超时持续时间未访问的嵌套 appender 被视为过期。过期的 appender 将被 SiftingAppender 关闭并移除。timeout 的默认值为 30 分钟。
maxAppenderCountintegerSiftingAppender 可以创建和跟踪的最大嵌套 appender 数量。maxAppenderCount 的默认值为 Integer.MAX_VALUE

SiftingAppender 通过动态创建嵌套 appender 来实现此功能。嵌套 appender 是根据配置文件中的模板创建的(在 <sift> 元素中指定)。SiftingAppender 负责管理子 appender 的生命周期。例如,SiftingAppender 将自动关闭并删除任何过期的 appender。当超过 timeout 参数指定的持续时间后,嵌套 appender 被视为过期。

在处理日志事件时,SiftingAppender 会选择一个子 appender 进行委托。选择标准由一个鉴别器(Discriminator)在运行时计算。用户可以通过 Discriminator 来指定选择标准。接下来我们来看一个示例。

示例

SiftExample 应用程序记录一条消息,表示应用程序已启动。然后它将 MDC 键 "userid" 设置为 "Alice" 并记录一条消息。以下是关键代码:

java
logger.debug("Application started");
MDC.put("userid", "Alice");
logger.debug("Alice says hello");

配置文件的模板演示了如何使用 SiftingAppender

示例:SiftingAppender 配置文件(logback-examples/src/main/resources/chapters/appenders/sift/byUserid.xml)

xml
<configuration>

  <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
    <!-- in the absence of the class attribute, it is assumed that the
         desired discriminator type is
         ch.qos.logback.classic.sift.MDCBasedDiscriminator -->
    <discriminator>
      <key>userid</key>
      <defaultValue>unknown</defaultValue>
    </discriminator>
    <sift>
      <appender name="FILE-${userid}" class="ch.qos.logback.core.FileAppender">
        <file>${userid}.log</file>
        <append>false</append>
        <layout class="ch.qos.logback.classic.PatternLayout">
          <pattern>%d [%thread] %level %mdc %logger{35} -%kvp -%msg%n</pattern>
        </layout>
      </appender>
    </sift>
  </appender>

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

如果没有指定 class 属性,则假定鉴别器类型是 MDCBasedDiscriminator。区分值是与 key 属性给定的 MDC 值关联的值。然而,如果该 MDC 值为 null,则使用 defaultValue 作为区分值。

SiftingAppender 在引用和配置子 appender 方面具有独特的能力。在上面的示例中,SiftingAppender 将创建多个 FileAppender 实例,每个 FileAppender 实例由与 "userid" MDC 键关联的值标识。每当 "userid" MDC 键被赋予新值时,将从头开始构建一个新的 FileAppender 实例。SiftingAppender 跟踪它创建的 appender。30 分钟内未使用的 appender 将自动关闭和丢弃。

变量导出

仅拥有不同的 appender 实例是不够的;每个实例必须输出到不同的目标资源。为了允许这种区分,模板中传递给鉴别器的键(在上面的示例中为 "userid")被导出,并成为 变量。因此,可以使用该变量来区分给定子 appender 使用的实际资源。

使用上述显示的 "byUserid.xml" 配置文件运行 SiftExample 应用程序将会产生两个不同的日志文件,"unknown.log" 和 "Alice.log"。

局部作用域变量

自版本 1.0.12 起,在配置文件中在本地作用域内定义的属性将对嵌套 appender 可用。此外,您可以 定义变量动态计算<sift> 元素内部。还支持在 <sift> 元素的外部和内部定义的部分组合变量。

设置合适的超时时间

对于某些类型的应用程序来说,设置超时参数可能会很困难。如果超时时间太短,嵌套的 appender 可能会在几秒后被移除,然后重新创建。这种现象称为 trashing。如果超时时间太长,并且 appender 被快速连续地创建,可能会耗尽资源。同样,将 maxAppenderCount 设置得太低也可能导致 trashing

在许多情况下,更容易找到代码中不再需要嵌套 appender 的位置。如果存在这样一个位置,即使是大概的,可以使用 FINALIZE_SESSION 标记从该位置记录日志。每当 SiftingAppender 看到标记为 FINALIZE_SESSION 的日志事件,它将结束关联的嵌套 appender 的生命周期。当达到生命周期末期时,嵌套 appender 将持续几秒钟以处理任何延迟到来的事件(如果有),然后关闭。

java
import org.slf4j.Logger;
import static ch.qos.logback.classic.ClassicConstants.FINALIZE_SESSION_MARKER;

  void job(String jobId) {

    MDC.put("jobId", jobId);
    logger.info("Starting job.");

    ... 执行工作需要做的事情

    // 会导致嵌套 appender 达到生命周期末期。它将持续几秒钟。
    logger.info(FINALIZE_SESSION_MARKER, "About to end the job");

    try {
      .. 执行清理工作
    } catch(Exception e);
      // 这条日志语句将由持续存在的 appender 处理。
      // 不会创建新的 appender。
      logger.error("unexpected error while cleaning up", e);
    }
  }

AsyncAppender

AsyncAppender 异步记录 ILoggingEvent。它仅充当事件分发器,因此必须引用另一个 appender 才能发挥作用。

默认情况下,如果占用 80% 的话会有丢失

AsyncAppenderBlockingQueue 中缓冲事件。由 AsyncAppender 创建的工作线程从队列头部获取事件,并将其分派到与 AsyncAppender 相关联的单个 appender。请注意,默认情况下,如果 AsyncAppender 的队列占据了 80% 的空间,它将丢弃级别为 TRACE、DEBUG 和 INFO 的事件。这种策略对性能有着非常有利的影响,但会造成事件丢失。

应用停止/重部署

在应用程序关闭或重新部署时,必须停止 AsyncAppender 以停止和回收工作线程,并从队列中刷新日志事件。这可以通过 停止 LoggerContext 来实现,它将关闭所有 appender,包括任何 AsyncAppender 实例。AsyncAppender 将等待工作线程在 maxFlushTime 指定的超时时间内刷新。如果在关闭 LoggerContext 时发现排队的事件被丢弃,可能需要增加超时时间。指定 maxFlushTime 为 0 将强制 AsyncAppender 在返回停止方法之前等待所有排队的事件被刷新。

关闭后清理

根据 JVM 关闭的模式,处理排队事件的工作线程可能会被中断,导致事件被滞留在队列中。这通常发生在 LoggerContext 未被干净地停止或者 JVM 在典型控制流之外终止时。为了避免在这些情况下中断工作线程,可以向 JVM 运行时插入一个关闭钩子,该钩子在 JVM 关闭被启动后 正确停止 LoggerContext。当其他关闭钩子尝试记录事件时,关闭钩子也可以是清除关闭 Logback 的首选方法。

这是 AsyncAppender 允许的属性列表:

属性名类型描述
queueSizeint阻塞队列的最大容量。默认情况下,queueSize 设置为 256。
discardingThresholdint默认情况下,当阻塞队列剩余容量为 20% 时,将丢弃级别为 TRACE、DEBUG 和 INFO 的事件,仅保留级别为 WARN 和 ERROR 的事件。要保留所有事件,请将 discardingThreshold 设置为 0。
includeCallerDataboolean提取调用者数据可能相当昂贵。为了提高性能,默认情况下,与事件相关联的调用者数据在添加到事件队列时不会被提取。默认情况下,只会复制像线程名称和 MDC 这样的“廉价”数据。您可以通过将 includeCallerData 属性设置为 true 来指示此 appender 包括调用者数据。
maxFlushTimeint根据队列深度和对应 appender 的延迟,AsyncAppender 可能需要花费不可接受的时间来完全刷新队列。当 LoggerContext 被停止时,AsyncAppenderstop 方法将等待工作线程完成的时间最长达到此超时时间。使用 maxFlushTime 指定以毫秒为单位的最大队列刷新超时时间。在此窗口内无法处理的事件将被丢弃。该值的语义与 Thread.join(long) 相同。
neverBlockboolean如果为 false(默认值),appender 将在向已满队列添加事件时阻塞,而不是丢失消息。如果设置为 true,appender 将仅丢弃消息,而不会阻塞您的应用程序。

默认情况下,事件队列配置为最大容量为 256 个事件。如果队列已满,则应用程序线程将被阻止记录新事件,直到工作线程有机会分派一个或多个事件。当队列不再处于最大容量时,应用程序线程就能够开始再次记录事件。因此,在 appender 在其事件缓冲区的容量处于或接近最大容量时,异步记录实际上变得伪同步。这未必是一件坏事。该 appender 被设计为允许应用程序继续运行,尽管在日志事件压力缓解之前记录事件需要稍微更多的时间。

针对最大应用程序吞吐量最佳调整 appender 事件队列大小的优化取决于几个因素。以下任何一个或所有因素都可能导致表现出伪同步行为:

  • 大量的应用程序线程
  • 每个应用程序调用的大量日志事件
  • 每个日志事件的大量数据
  • 子 appender 的高延迟

为了保持事物顺利进行,增加队列的大小通常会有所帮助,但会以减少应用程序可用堆空间为代价。

丢失行为

鉴于上述讨论并为了减少阻塞,AsyncAppender 默认情况下,当队列剩余容量低于 20% 时,将丢弃级别为 TRACE、DEBUG 和 INFO 的事件,仅保留级别为 WARN 和 ERROR 的事件。该策略确保处理日志事件时不会阻塞(因此性能优秀),但会以低于 20% 容量时丢失级别为 TRACE、DEBUG 和 INFO 的事件为代价。通过将 discardingThreshold 属性设置为 0(零)可以避免事件丢失。

示例:AsyncAppender 配置(logback-examples/src/main/resources/chapters/appenders/conc/logback-async.xml)

传统

xml
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>%logger{35} -%kvp -%msg%n</pattern>
    </encoder>
  </appender>

  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE" />
  </appender>

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

规范(1.3)

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>

<configuration>
  <import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
  <import class="ch.qos.logback.classic.AsyncAppender"/>
  <import class="ch.qos.logback.core.FileAppender"/>

  <appender name="FILE" class="FileAppender">
    <file>myapp.log</file>
    <encoder class="PatternLayoutEncoder">
      <pattern>%logger{35} -%kvp -%msg%n</pattern>
    </encoder>
  </appender>

  <appender name="ASYNC" class="AsyncAppender">
    <appender-ref ref="FILE"/>
  </appender>

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

编写自己的 Appender

您可以通过继承 AppenderBase 来轻松地编写自己的 appender。它处理了大多数 appender 共享的过滤器、状态消息和其他功能。派生类只需要实现一个方法,即 append(Object eventObject)

下面列出的 CountingConsoleAppender 在控制台上附加了一定数量的事件。达到限制后,它将关闭。它使用 PatternLayoutEncoder 来格式化事件,并接受一个名为 limit 的参数。因此,除了 append(Object eventObject) 之外,还需要几个方法。如下所示,这些参数由 logback 的各种配置机制自动处理。

示例 4:CountingConsoleAppender(logback-examples/src/main/java/chapters/appenders/CountingConsoleAppender.java)

java
package chapters.appenders;

import java.io.IOException;

import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;


public class CountingConsoleAppender extends AppenderBase<ILoggingEvent> {
  static int DEFAULT_LIMIT = 10;
  int counter = 0;
  int limit = DEFAULT_LIMIT;

  PatternLayoutEncoder encoder;

  public void setLimit(int limit) {
    this.limit = limit;
  }

  public int getLimit() {
    return limit;
  }

  @Override
  public void start() {
    if (this.encoder == null) {
      addError("No encoder set for the appender named ["+ name +"].");
      return;
    }

    try {
      encoder.init(System.out);
    } catch (IOException e) {
    }
    super.start();
  }

  public void append(ILoggingEvent event) {
    if (counter >= limit) {
      return;
    }
    // output the events as formatted by our layout
    try {
      this.encoder.doEncode(event);
    } catch (IOException e) {
    }

    // prepare for next event
    counter++;
  }

  public PatternLayoutEncoder getEncoder() {
    return encoder;
  }

  public void setEncoder(PatternLayoutEncoder encoder) {
    this.encoder = encoder;
  }
}

start() 方法检查是否存在 PatternLayoutEncoder。如果未设置编码器,则 appender 无法启动并发出错误消息。

这个自定义 appender 说明了两点:

  • 遵循 setter/getter JavaBeans 约定的所有属性都由 logback 配置器透明处理。在 logback 配置期间自动调用的 start() 方法负责验证 appender 的各个属性是否设置并且是否一致。
  • AppenderBase.doAppend() 方法调用其派生类的 append() 方法。实际的输出操作发生在 append() 方法中。特别是在这个方法中,appender 通过调用它们的布局来格式化事件。

CountingConsoleAppender 可以像其他 appender 一样进行配置。有关示例配置文件 logback-examples/src/main/resources/chapters/appenders/countingConsole.xml 的示例,请参见。

Logback Access

在 logback-classic 中找到的大多数 appender 也在 logback-access 中有相应的版本。它们的工作方式与 logback-classic 的对应 appender 基本相同。在接下来的部分,我们将介绍它们的使用方法。

SocketAppender 和 SSLSocketAppender

SocketAppender 设计用于通过传输序列化的 AccessEvent 对象来记录到远程实体。就访问事件而言,远程日志记录是非侵入性的。在反序列化后,可以像本地生成的事件一样记录该事件。

SSLSocketAppender 扩展了基本的 SocketAppender,允许通过安全套接字层(SSL)记录到远程实体。

access 的 SocketAppender 的属性与 classic 的 SocketAppender 可用属性相同。

ServerSocketAppender 和 SSLServerSocketAppender

SocketAppender 类似,ServerSocketAppender 设计用于通过传输序列化的 AccessEvent 对象来记录到远程实体。但是,当使用 ServerSocketAppender 时,appender 充当服务器,被动地侦听 TCP 套接字,等待来自感兴趣的客户端的入站连接。传递给 appender 的日志事件将分发到所有连接的客户端。

SSLSocketAppender 扩展了基本的 ServerSocketAppender,允许通过安全套接字层(SSL)记录到远程实体。

access 的 ServerSocketAppender 的属性与 classic 的 ServerSocketAppender 可用属性相同。

SMTPAppender

Access 的 SMTPAppender 与其经典对应项的工作方式相同。但是,评估器选项则有所不同。默认情况下,SMTPAppender 使用一个 URLEvaluator 对象。该评估器包含一个 URL 列表,用于与当前请求的 URL 进行匹配。当其中一个页面被 URLEvaluator 请求时,SMTPAppender 会发送一封电子邮件。

以下是在访问环境中配置 SMTPAppender 的示例配置。

示例:SMTPAppender 配置 (logback-examples/src/main/resources/chapters/appenders/conf/access/logback-smtp.xml)

xml
<appender name="SMTP"
  class="ch.qos.logback.access.net.SMTPAppender">
  <layout class="ch.qos.logback.access.html.HTMLLayout">
    <pattern>%h%l%u%t%r%s%b</pattern>
  </layout>

  <Evaluator class="ch.qos.logback.access.net.URLEvaluator">
    <URL>url1.jsp</URL>
    <URL>directory/url2.html</URL>
  </Evaluator>
  <from>sender_email@host.com</from>
  <smtpHost>mail.domain.com</smtpHost>
  <to>recipient_email@host.com</to>
</appender>

通过这种触发电子邮件的方式,用户可以选择对特定流程中重要的页面进行监控。例如,当访问了此类页面时,将发送一封带有先前访问过的页面以及用户想要包含在电子邮件中的任何信息的电子邮件。

DBAppender

DBAppender 用于将访问事件插入数据库。

在 logback 版本 1.2.8 中,DBAppender 不再随 logback-access 一起提供。然而,logback-access 的 DBAppender 可以通过以下 Maven 坐标获得:

ch.qos.logback.db:logback-access-db:1.2.11.1

DBAppender 使用两个表:access_eventaccess_event_header。在使用 DBAppender 之前,这两个表必须存在。Logback 附带了用于创建这些表的 SQL 脚本。这些脚本可以在 logback-access/src/main/java/ch/qos/logback/access/db/script 目录中找到。对于最常见的数据库系统,每种系统都有一个专门的脚本。如果您特定类型的数据库系统的脚本丢失,那么编写一个脚本应该非常容易,只需参考现有脚本之一即可。欢迎您贡献这些缺失的脚本到该项目。

access_event 表的字段如下所示:

字段类型描述
timestampbig int访问事件创建时的时间戳。
requestURIvarchar请求的 URI。
requestURLvarchar请求的 URL。这是由请求方法、请求 URI 和请求协议组成的字符串。
remoteHostvarchar远程主机的名称。
remoteUservarchar远程用户的名称。
remoteAddrvarchar远程 IP 地址。
protocolvarchar请求协议,如 HTTPHTTPS
methodvarchar请求方法,通常为 GETPOST
serverNamevarchar发出请求的服务器名称。
event_idint访问事件的数据库 ID。

access_event_header 表包含每个请求的头信息,其组织形式如下:

字段类型描述
event_idint对应访问事件的数据库 ID。
header_keyvarchar头部名称,例如 User-Agent
header_valuevarchar头部值,例如 Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) Gecko/20061010 Firefox/2.0

经典的 DBAppender 的所有属性在 accessDBAppender 中都可用。后者提供了一个额外的选项,如下所述。

属性名称类型描述
insertHeadersboolean告诉 DBAppender 是否将所有传入请求的头信息填充到数据库中。

以下是使用 DBAppender 的示例配置。

示例:DBAppender 配置 (logback-examples/src/main/resources/chapters/appenders/conf/access/logback-DB.xml)

xml
<configuration>

  <appender name="DB" class="ch.qos.logback.access.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
      <driverClass>com.mysql.jdbc.Driver</driverClass>
      <url>jdbc:mysql://localhost:3306/logbackdb</url>
      <user>logback</user>
      <password>logback</password>
    </connectionSource>
    <insertHeaders>true</insertHeaders>
  </appender>

  <appender-ref ref="DB" />
</configuration>

SiftingAppender

logback-access 中的 SiftingAppender 与其 logback-classic 对应项非常相似,主要区别在于 logback-access 中的默认判别器不是基于 MDC 的。正如其名称所示,AccessEventDiscriminator 使用 AccessEvent 中的指定字段作为选择嵌套 appender 的依据。如果指定字段的值为 null,则使用 defaultValue 属性中指定的值。

指定的 AccessEvent 字段可以是 COOKIE、REQUEST_ATTRIBUTE、SESSION_ATTRIBUTE、REMOTE_ADDRESS、LOCAL_PORT、REQUEST_URI 中的一个。请注意,前三个字段需要同时指定 AdditionalKey 属性。

以下是一个示例配置文件。

示例:SiftingAppender 配置 (logback-examples/src/main/resources/chapters/appenders/conf/sift/access-siftingFile.xml)

xml
<configuration>
  <appender name="SIFTING" class="ch.qos.logback.access.sift.SiftingAppender">
    <Discriminator class="ch.qos.logback.access.sift.AccessEventDiscriminator">
      <Key>id</Key>
      <FieldName>SESSION_ATTRIBUTE</FieldName>
      <AdditionalKey>username</AdditionalKey>
      <defaultValue>NA</defaultValue>
    </Discriminator>
    <sift>
       <appender name="ch.qos.logback:logback-site:jar:1.3.8" class="ch.qos.logback.core.FileAppender">
        <file>byUser/ch.qos.logback:logback-site:jar:1.3.8.log</file>
        <layout class="ch.qos.logback.access.PatternLayout">
          <pattern>%h %l %u %t \"%r\" %s %b</pattern>
        </layout>
      </appender>
    </sift>
  </appender>
  <appender-ref ref="SIFTING" />
</configuration>

在上面的配置文件中,SiftingAppender 嵌套了 FileAppender 实例。键 "id" 被指定为一个变量,将在嵌套的 FileAppender 实例中可用。默认的判别器 AccessEventDiscriminator 将在每个 AccessEvent 中搜索名为 "username" 的会话属性。如果没有找到这样的属性,则使用默认值 "NA"。因此,假设名为 "username" 的会话属性包含每个登录用户的用户名,则每个用户在当前文件夹下的 byUser/ 文件夹中都会有一个以用户名称命名的日志文件,其中包含该用户的访问日志。