logback官方文档中文翻译第四章:Appenders 续

SMTPAppender

SMTPAppender 收集日志事件到一个或多个固定大小的缓冲区,当用户指定的事件发生时,将从缓冲区中取出适当的内容进行发送。SMTP 邮件是异步发送的。默认情况下,当日志的级别为 ERROR 时,邮件发送将会被触发。而且默认的情况下,所有事件都使用同一个缓冲区。

SMTPAppender 的属性如下表所示:

属性名 类型 描述
smtpHost String SMTP 服务器的主机名。强制性的。
smtpPort int SMPT 服务监听的端口。默认为 25.
to String 接收者的邮件地址。触发事件发送给接收者。多个收件人可以使用逗号(,)分隔,或者使用多个 <to> 元素来指定。
from String SMTPAppender 使用的发件人,格式遵循邮件通用格式,如果你想要包含发送者的名字,使用这种格式 " Adam Smith <[email protected]>",那么邮件将会显示收件人为 " Adam Smith <[email protected]>"
subject String 邮件的主题。它可以是通过 PatternLayout 转换后的有效值。关于 Layout 将在接下来的章节讨论。<br />邮件应该有一个主题行,对应触发的邮件信息。<br />假设 subject 的值为:"Log: %Logger - %msg",触发事件的 logger 名为 "com.foo.Bar",并且日志信息为 "Hello world"。那么发出的邮件信息将会有一个名为 "Log: com.foo.Bar - Hello World" 的主题行。<br />默认情况下,这个属性的值为 "%logger{20} - %m"
discriminator Discriminator 在 Discriminator 的帮助下,SMTPAppender 根据 discriminator 返回的值可以将不同日志事件分散到不同的缓冲区中。默认的 discriminator 将返回同一个值,所以所有的事件都使用同一个缓冲区。
evaluator IEvaluator 通过创建一个新的 <EventEvaluator/> 元素来声明此选项。通过 class 属性指定 class 的名字表示用户希望通过哪个类来满足 SMTPAppenderEvaluator 的需要。<br />如果没有指定此选项,当触发一个大于等于 ERROR 级别的事件时,SMTPAppender 将会被分配一个 OnErrorEvaluator 的实例。<br />logback 配备了几个其它的 evaluator,分别叫 OnMarkerEvaluator (将在下面讨论),一个相对强大的 evaluator 叫 JaninoEventEvaluator(在其它章节讨论) 以及最近版本才有的一个更加强大的 evaluator 叫 GEventEvaluator
cyclicBufferTracker CyclicBufferTracker 从名字可以看出,是一个 CyclicBufferTracker 的实例追踪循环缓冲区。它基于 discriminator 返回的 key (见上)。<br />如果你不想指定一个 cyclicBufferTracker,那么将会自动创建一个 CyclicBufferTracker 的实例。默认的,这个实例用来保留事件的循环缓冲区的大小为 256。你需要改变 bufferSize 选项的大小(见下面)
username String 默认为 null
password String 默认为 null
STARTTLS boolean 如果为 true,那么 appender 将会发送 STARTTLS 命令(如果服务器支持)将连接变成 SSL 连接。注意,连接初始的时候是为加密的。默认为 false。
SSL boolean 如果为 true,将通过 SSL 连接服务器。默认为 false。
charsetEncoding String 邮件信息将会通过 charset 进行编码。默认编码为 "UTF-8"
localhost String 一旦 SMTP 客户端的主机名没有配置正确,例如客户端的 hostname 不是全限定的,那么服务端会拒绝客户端发送的 HELO/EHLO 命令。为了解决这个问题,你可以将 localhost 的值设置为客户端主机的全限定名。详情见 com.sun.mail.smtp 包文档中的 "mail.smtp.localhost" 属性。(这个网站已经关闭了...)
asynchronousSending boolean 决定邮件传输是否是异步进行。默认为 'true'。但是,在某些特定的情况下,异步发送不怎么合适。例如,当发生一个严重错误时,你的应用使用 SMTPAppender 去发送一个警告,然后退出。但是相关线程可能没有时间去发送警告邮件。在这种情况下,你可以设置该属性的值为 'false'。
includeCallerData boolean 默认为 false。如果 asynchronousSending 的值为 true,并且你希望在日志中看到调用者的信息,你可以设置该属性的值为 true
sessionViaJNDI boolean SMTPAppender 基于 javax.mail.Session 来发送邮件信息。默认情况下,该属性的值为 false,所以需要用户指定相关属性通过 SMTPAppender 来构建 javax.mail.Session 实例。如果设置为 true,javax.mail.Session 实例将会通过 JNDI 来获取。参见 jndiLocation 属性。<br />通过 JNDI 获取 Session 实例可以减少需要配置的数量,使你的应用减少重复(dryer)的工作。更多关于在 Tomcat 配置 JNDI 的信息请参考 JNDI Resources How-to。<br />注意:通过 JNDI 获取 Session 的时候请移除 web 应用下 WEB-INF/lib 文件夹下的 mail.jaractivation.jar
jndiLocation String JNDI 中放置 javax.mail.Session 的地方。默认为:" java:comp/env/mail/Session "

SMTPAppender 仅仅只在它的循环缓存区中保留最后 256 个日志事件,当缓存区快要满的时候丢掉旧的日志事件。因此,通过 SMTPAppender 发送任何邮件包含的日志事件都不会超过 256 个。这在保留内存需求的限制,还提供了数量可观的应用上下文。

SMTPAppender 基于 JavaMail API。在 JavaMail 1.4 版本做过测试。JavaMail 需要 JavaBeans Activation Framework 包。你可以去它们各自的网站下载 JavaMail APIJavaBeans Activation Framework。在运行下面的示例之前先确保将这两个 jar 包放在 classpath 下。

chapters.appenders.mail.EMail 应用会生成多个日志信息,随后再生成一个错误日志信息。它接收两个参数,第一参数是整形,表示需要生成多少个日志事件。第二个参数表示 logback 的配置文件。Email 最后生成一个错误日志,将会触发发送邮件信息。

下面是一个 Email 应用的简单配置信息:

Example: mail1

<configuration>
    <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
        <smtpHost>SMTP 服务器的地址</smtpHost>
        <to>收件人1</to>
        <to>收件人2</to>
        <from>发件人</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>

在使用以上配置测试 chapters.appenders.mail.Email 之前,你需要设置 smtpHosttofrom 的值。一旦你正确设置了配置文件的值,你可以通过一下命令来执行:

java chapters.appenders.mail.EMail 100 src/main/java/chapters/appenders/mail/mail1.xml

收件者收到的邮件是经过 PatternLayout 格式化后的 100 条日志。下图展示的就是 Mozilla Thunderbird 邮件客户端接收到的邮件。

[图片上传失败...(image-2a730a-1547968537757)]

下个例子配置文件 mail2.xml 中的 smtpHosttofrom 属性的值通过占位符来代替。下面是 mail2.xml 配置中的一部分:

<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
  <smtpHost>${smtpHost}</smtpHost>
  <to>${to}</to>
  <from>${from}</from>
  <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
</appender>

你可以通过命令行来传递参数:

java [email protected] [email protected] -DsmtpHost=some_smtp_host \
  chapters.appenders.mail.EMail 10000 src/main/java/chapters/appenders/mail/mail2.xml

根据你的环境替换合适的值。

注意在新的例子中,PatternLayoutHTMLLayout 替代,将日志格式化为 HTML 表格。你可以更改行与列的顺序,以及表格的 CSS 样式。查看 HTMLLayout 文档更详细的信息。

由于给定的循环缓冲区的大小为 256,收件人可以看到经过 256 条经过格式化的日志显示在 HTML 表格中。注意,chapters.appenders.mail.Email 应用生成了 10'000 条日志,但是只有最新的 256 条日志会显示在邮件中。

[图片上传失败...(image-22586f-1547968537757)]

像 Mozilla Thunderbird, Eudora or MS Outlook 这些邮件客户端,提供了非常好的 CSS 样式来支持 HTML 邮件。但是,有时候会自动将 HTML 格式变成文本格式。如果想在 Thunderbird 查看 HTML 格式的邮件,需要通过 "View→Message Body As→Original HTML" 选项来进行设置。Yahoo 邮箱对 HTML 邮件有非常好的 CSS 样式支持。另一方面对 Gmail 来说,虽然它支持基本 HTML 表结构,但是它会忽略内部的 CSS 样式。Gmail 支持内联的 CSS 样式,但是由于内联的 CSS 会使输出结果变得庞大,所以 HTMLLayout 不会使用内联的 CSS 样式。

定制缓冲区大小

默认情况下,SMTPAppender 会输出最新的 256 条日志信息。下面一个例子设置了不同缓冲区大小。

Example: customBufferSize.xml

<configuration>
    <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
        <smtpHost>${smtpHost}</smtpHost>
        <to>${to}</to>
        <from>${from}</from>
        <subject>%logger{20} - %m</subject>
        <layout class="ch.qos.logback.classic.html.HTMLLayout" />

        <cyclicBufferTracker
            class="ch.qos.logback.core.spi.CyclicBufferTracker">
            <!-- 每封邮件只包含一条日志 -->
            <bufferSize>1</bufferSize>
        </cyclicBufferTracker>
    </appender>

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

触发事件

如果 evaluator 属性没有设置,当日志事件的级别为 ERROR 时,SMTPAppender 会创建一个 OnErrorEvaluator 实例来触发发送邮件。发送错误时发送邮件是比较合理的,EventEvaluator 提供了不同的实现来重写默认的方法。

SMTPAppender 提交的每一个日志事件通过 evaluator 都会调用 evaluate() 方法来决定这个事件是否需要发送邮件,或者仅仅是替换缓冲区中的内容。当 evaluator 给了一个确定的答案,那么将会发送邮件。SMTPAppender 有且只有一个 evaluator 实例。这个实例能够管理它自己的内部状态。为了对这个进行说明,CounterBasedEvaluator 实现了一个 evaluator,当第 1024 个日志事件到来时才会触发邮件发送。

Example: CounterBasedEvaluator.java

package chapters.appenders.mail;

import ch.qos.logback.core.boolex.EvaluationException;
import ch.qos.logback.core.boolex.EventEvaluator;
import ch.qos.logback.core.spi.ContextAwareBase;

public class CounterBasedEvaluator extends ContextAwareBase implements EventEvaluator {

  static int LIMIT = 1024;
  int counter = 0;
  String name;

  public boolean evaluate(Object event) throws NullPointerException,
      EvaluationException {
    counter++;

    if (counter == LIMIT) {
      counter = 0;

      return true;
    } else {
      return false;
    }
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

这个类继承了 ContextAwareBase 以及实现了 EventEvaluator 接口。这可以让用户专注在 EventEvaluator 的核心功能上,让基类提供基础的功能。

设置 SMTPAppenderevaluator 选项来指定用户自定义的 evaluator。下面的例子将 SMTPAppender 附加在 root logger 上,并使用 CounterBasedEvaluator 作为它事件的 evaluator。

Example: mail3.xml

<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="chapters.appenders.mail.CounterBasedEvaluator" />
    <smtpHost>${smtpHost}</smtpHost>
    <to>${to}</to>
    <from>${from}</from>
    <subject>%logger{20} - %m</subject>

    <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
  </appender>

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

基于标记(Marker)触发

虽然通过默认通过错误级别的日志来触发邮件的发送是合理的,但是可能会导致太多的邮件充斥用户的邮箱。logback 自带了另外一个触发策略,叫做 OnMarkerEvaluator。它基于标记来触发。其实就是通过用户指定的标记来触发。下面这个例子说明这个一点:

Marked_EMail 应用包含了几个日志语句,都是 ERROR 级别的。但是只有其中的一条被标记。下面的是相关的代码:

Marker notifyAdmin = MarkerFactory.getMarker("NOTIFY_ADMIN");
logger.error(notifyAdmin,
  "This is a serious an error requiring the admin's attention",
   new Exception("Just testing"));

下面的配置文件,表示当存在日志事件被标记为 NOTIFY_ADMIN 或者 TRANSACTION_FAILURE 将会触发邮件发送。

Example: mailWithMarker.xml

<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
      <marker>NOTIFY_ADMIN</marker>
      <!-- 你可以指定多个标记 -->
      <marker>TRANSACTION_FAILURE</marker>
    </evaluator>
    <smtpHost>${smtpHost}</smtpHost>
    <to>${to}</to>
    <from>${from}</from>
    <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
  </appender>

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

通过以下命令执行:

java [email protected] [email protected] -DsmtpHost=some_smtp_host \
  chapters.appenders.mail.Marked_EMail src/main/java/chapters/appenders/mail/mailWithMarker.xml

基于标记触发的 JaninoEventEvaluator

除了使用核心的 OnMarkerEvaluator,我们还可以是使用更加通用的 JaninoEventEvaluator,甚至更加强大的 GEventEvaluator。例如,下面的配置文件使用 JaninoEventEvaluator 而不是 OnMarkerEvaluator,但是它们的含义是一样的。

Example: mailWithMarker_Janino.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>    
    ... 跟之前一样
  </appender>
</configuration>

基于标记触发的 GEventEvaluator

通过使用 GEventEvaluator 来实现一个相同的 evaluator。

Example: mailWithMarker_GEvent.xml

<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator">
      <expression>
        e.marker?.contains("NOTIFY_ADMIN") || e.marker?.contains("TRANSACTION_FAILURE")
      </expression>
    </evaluator>    
    ... 跟之前一样
  </appender>
</configuration>

因为日志事件可能没有 marker,所以 marke 的值可能为 null。可以使用 Groovy 的安全解引用操作符,也就是 . ? 操作符。

STARTTLS/SSL 认证

SMTPAppender 支持通过用户名/密码以及 STARTTLS,SSL 协议进行认证。STARTTLS 跟 SSL 的不同之处在于,STARTTLS 初始化连接不是加密的,仅仅只有在客户端发出 STARTTLS 命令的时候将连接变为 SSL。在 SSL 模式下,连接在一开始就是被加密的。

Gmail 的 SMTPAppender 配置 (SSL)

下面的例子是 Gmail SSL 协议的 SMTPAppender 配置:

Example: gmailSSL.xml

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

    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to> <!-- additional destinations are possible -->
    <from>[email protected]</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>

译者注:Gmail 的配置我没有测试成功,一直发送不了邮件。最后还是配置的 163 邮箱

Gmail 的 SMTPAppender 配置 (STARTTLS)

下面的例子是 Gmail STARTTLS 协议的 SMTPAppender 配置:

Example: gmailSTARTTLS.xml

<configuration>   
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>587</smtpPort>
    <STARTTLS>true</STARTTLS>
    <username>[email protected]</username>
    <password>YOUR_GMAIL_xPASSWORD</password>
    
    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to> <!-- additional destinations are possible -->
    <from>[email protected]</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>

SMTPAppender 与 MDCDiscriminator

之前提到过,指定一个 discriminator 而不是使用默认的。根据指定的 discriminator,SMTPAppender 会生成关于特定用户,用户 session,客户端 IP 地址的邮件信息。

下面是 MDCBasedDiscriminator 与一个名叫 req.remoteHost 的 MDC key 结合使用的一个例子。假定该 key 已经包含了远程主机的 IP 地址。在 web 应用中,你可以使用 MDCInsertingServletFilter 去填充 MDC 的值。

Example: mailWithMDCBasedDiscriminator.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>

所以,SMTPAppender 发送的每一封邮件都有一个独特的远程主机,这非常利于定位问题。

在繁忙的应用中进行缓冲区管理

在内部,discriminator 返回每个不同的值都会创建一个新的循环缓冲区。但是,会维护一个 maxNumberOfBuffers 变量 (默认为 64)。当缓冲区的数量超过 maxNumberOfBuffers 时,最近最少更新的缓冲区会被丢弃。第二个安全策略是,最近 30 分钟没有被更新的缓冲区会被丢弃。

在每分钟有大量事务的机器上,设置一个较小值的 maxNumberOfBuffers,将会导致邮件中的日志数量变得特别小。实际上,在存在大量事务的情况下,连续生成同一个 discriminator 值会导致多个缓冲区会与同一个事务相关联,因为缓冲区会被 kill 掉再重建。即使在繁忙的系统中,循环缓冲区的最大数量也会被 maxNumberOfBuffers 所限制。

为了避免这种悠悠球效应(摇摆不定),当日志时间被标记为 "FINALIZE_SESSION " 时, SMTPAppender 会释放与给定 discriminator 值相关联的缓冲区。这将会导致在每个事务快要结束的时候,将会丢弃适当的缓冲区。你可以在避免 OOM 的前提下,增加 maxNumberOfBuffers 的值到 512 或 1024。

这里有三个完全不同但是又互补的机制一起管理循环缓冲区。它们可以确保即使在繁忙的系统中,也会让相关的缓冲区存活。

DBAppender

DBAppender 以一种独立于 JAVA 语言的方式将日志事件插入到三张数据库表中。

这三张表分别为:logging_event, logging_event_propertylogging_event_exception。在使用 DBAppender 之前,它们必须存在。logback 自带 SQL 脚本来创建表。这些脚本在 logback-classic/src/main/java/ch/qos/logback/classic/db/script 文件夹下。每一种最流行的数据库都有一个对应的脚本。如果没有你指定的数据库脚本,参考已经存在的例子,可以很简单的写一个。如果你把他们发给我们,我们很乐意在将来的版本中发布这些脚本。

如果你的 JDBC 驱动支持 JDBC 3.0 specification 规范中的 getGeneratedKeys 方法,并且你也创建了上述所需要色数据库表,那么不再需要额外的步骤。否则的话,你必须选择一个适合你数据库的 SQLDialect。目前 logback 支持的数据库方言有 H2, HSQL, MS SQL Server, MySQL, Oracle, PostgreSQL, SQLLite and Sybase。

下面的表格总结了数据库类型,以及它们是否支持 getGeneratedKeys() 方法:

RDBMS 测试版本 JDBC 驱动的测试版本 是否支持 getGeneratedKeys() logback 是否提供对应的方言
DB2 untested untested unknown NO
H2 1.2.132 - unknown YES
HSQL 1.8.0.7 - NO YES
Microsoft SQL Server 2005 2.0.1008.2 (sqljdbc.jar) YES YES
MySQL 5.0.22 5.0.8 (mysql-connector.jar) YES YES
PostgreSQL 8.x 8.4-701.jdbc4 NO YES
Oracle 10g 10.2.0.1 (ojdbc14.jar) YES YES
SQLLite 3.7.4 - unknown YES
Sybase SQLAnywhere 10.0.1 - unknown YES

在 "标准的" PC 电脑上,经过测试,将单个的日志事件写入数据库需要花费大约 10 毫秒。如果使用线程池,大约只需要花费大约 1 毫秒。大部分的 JDBC 驱动都支持连接池。

不是很懂这个 "标准的" PC 电脑,到底是台什么电脑

根据连接数据库的工具以及数据库自身,配置 logback 去使用 DBAppender 可以通过几种不同的方式去实现。我们很快就会发现,配置 DBAppender 的主要问题在于如何设置 ConnectionSource 实例。

一旦为数据库配置了 DBAppender,日志事件就会被发送到指定的数据库中。根据之前说的,logback 使用三张表来存储日志数据。

logging_event 表包含了以下字段:

Field Type Description
timestamp big int 日志事件的创建时间
formatted_message text 经过 org.slf4j.impl.MessageFormatter 格式化后的消息
logger_name varchar 发出日志的 logger 名
level_string varchar 日志事件的级别
reference_flag smallint 用来表示是否是异常或者与 MDC 属性相关联。它的值通过 ch.qos.logback.classic.db.DBHelper 计算得到。日志时间包含 MDC 或者 Context 时,它的值为 1。包含异常时,它的值为 2。包含两者,则值为 3
caller_filename varchar 发出日志请求的文件名
caller_class varchar 发出日志请求的类
caller_method varchar 发出日志请求的方法
caller_line char 发出日志请求所在的行
event_id int 日志事件在数据库的 id

logging_event_property 表用于存储 MDC 或者 Context 中的 key 与 value。它包含如下字段:

Field Type Description
event_id int 日志事件的数据库 id
mapped_key varchar MDC 属性的 key
mapped_value text MDC 属性的 value

logging_event_exception 表包含如下字段:

Field Type Description
event_id int 日志事件的数据库 id
i smallint 堆栈所在的行
trace_line varchar 相对应的堆栈信息

下面给出一个更加直观的示例,截图上面的信息是 MySQL 数据中 DBAppender 提供的内容。

logging_event

[图片上传失败...(image-127464-1547968537757)]

logging_event_exception

[图片上传失败...(image-6e354a-1547968537757)]

logging_event_property

[图片上传失败...(image-c61fc9-1547968537757)]

ConnectionSource

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

第一个例子我们使用 DriverManagerConnectionSource 与 MySQL database,如下所示:

Example: append-toMySQL-with-driverManager.xml

<configuration>

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
      <driverClass>com.mysql.jdbc.Driver</driverClass>
      <url>jdbc:mysql://host_name:3306/datebase_name</url>
      <user>username</user>
      <password>password</password>
    </connectionSource>
  </appender>
  
  <root level="DEBUG" >
    <appender-ref ref="DB" />
  </root>
</configuration>

必须正确配置 JDBC 驱动,这里使用 com.mysql.jdbc.Driverurl 必须以 jdbc:mysql:// 开头。

DriverManagerConnectionSource 实现了 ConnectionSource 接口,通过基于 URL 的传统 JDBC 方式来获取连接。

这个类为每一个调用 getConnection() 的方法都新建一个 Connection 连接。推荐你使用本地支持的连接池的 JDBC 驱动,或者创建你自己实现的 ConnectionSource,基于你已经使用的任何连接池机制。如果你可以使用支持 javax.sql.DataSource 的 JNDI 实现,例如,在 J2EE 应用服务中,参见下面的 JNDIConnectionSource

Example: append-with-datasource.xml

<configuration  debug="true">

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
     <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
       
       <dataSource class="${dataSourceClass}">
         <!-- Joran 不能替换不是属性的变量。因此我们不能像其它变量一样声明接下来的变量
         -->
         <param name="${url-key:-url}" value="${url_value}"/>
         <serverName>${serverName}</serverName>
         <databaseName>${databaseName}</databaseName>
       </dataSource>
       
       <user>${user}</user>
       <password>${password}</password>
     </connectionSource>
  </appender>

  <root level="INFO">
    <appender-ref ref="DB" />
  </root>  
</configuration>

在这个例子中,我们大量使用了变量替换。当需要把一些连接的细节集中在一个配置文件中,并且通过 logback 与其它框架共享时非常方便。

JNDIConnectionSource

JNDIConnectionSource 是 logback 自带的,ConnectionSource 的另一种实现。从名字可以看出来,它通过 JNDI 获取 javax.sql.DataSource,然后再获取 java.sql.Connection 实例。JNDIConnectionSource 主要设计用在 J2EE 应用服务器以及应用服务器客户端中,这里假设应用服务器支持远程获取 javax.sql.DataSource。因为可以利用连接池或者其它应用服务器所提供的好处。更加重要的是,你的应用不需要做重复的工作,因为不需要在 logback.xml 中定义一个 DataSource

例如,下面的是关于 Tomcat 的一个配置片段,它是基于 PostgreSQL。当然,上面提到其它数据也可以。

<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>

一旦 DataSource 在你的 J2EE 服务中定义了,你可以轻松的在 logback 配置文件中引用。如下所示:

Example: append-via-jndi.xml

<configuration debug="true">
  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.JNDIConnectionSource">
      <!-- please note the "java:comp/env/" prefix -->
      <jndiLocation>java:comp/env/jdbc/logging</jndiLocation>
    </connectionSource>
  </appender>
  <root level="INFO">
    <appender-ref ref="DB" />
  </root>  
</configuration>

这个类通过午餐构造函数获取一个 javax.naming.InitialContext。在 J2EE 环境通常可以行得通。但是在 J2EE 环境之外,你需要根据 JNDI 提供者的文档提供一个 jndi.properties 属性文件。

连接池

日志事件可以很快的被创建。为了让日志事件都能被插入到数据库,推荐 DBAppender 使用连接池配置。

经过实验发现,使用连接池,可以让 DBAppender 有大幅的性能提升。下面的配置文件,将日志事件发送给 MySQL,没有使用连接池。

Example: append-toMySQL-with-datasource.xml

<configuration>

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
      <dataSource class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
        <serverName>${serverName}</serverName>
        <port>${port$</port>
        <databaseName>${dbName}</databaseName>
        <user>${user}</user>
        <password>${pass}</password>
      </dataSource>
    </connectionSource>
  </appender>
    
  <root level="DEBUG">
    <appender-ref ref="DB" />
  </root>
</configuration>

在这个配置文件中,发送 500 个日志事件到 MySQL 数据库,需要高达 5 秒的时间,相当每条请求需要 10 毫秒。在大型的应用中,这个数字是不能够被接受的。

DBAppender 连接池需要使用一个专业的外部库。下一个例子中使用 c3p0。为了使用 c2p0,你需要下载并将 c3p0-VERSION.jar 放在类路径下。

Example: append-toMySQL-with-datasource-and-pooling.xml

<configuration>

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource
      class="ch.qos.logback.core.db.DataSourceConnectionSource">
      <dataSource
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <driverClass>com.mysql.jdbc.Driver</driverClass>
        <jdbcUrl>jdbc:mysql://${serverName}:${port}/${dbName}</jdbcUrl>
        <user>${user}</user>
        <password>${password}</password>
      </dataSource>
    </connectionSource>
  </appender>

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

使用这个新的配置,发送 500 条日志事件到 MySQL 数据库大约需要 0.5 秒,大约 1 毫秒一条请求,性能提升了十倍。

SyslogAppender

syslog 协议非常的简单:syslog 发送者将信息发送给 syslog 接收者。接收者通常叫做 syslog 守护线程 或者 syslog 服务器。logback 可以把消息发送给远程的 syslog 守护线程。通过 SyslogAppender 可以实现。

下面是 SyslogAppender 的属性:

属性名 类型 描述
syslogHost String syslog 服务器的主机名
port String 用来 syslog 服务器的端口号。默认端口为 514,这个情况下,不需要修改。
facility String 用来确定消息的来源。<br />它的值必须为 KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, NTP, AUDIT, ALERT, CLOCK, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7 其中之一。大小写不敏感
suffixPattern String 该属性指定发送到 syslog 服务器的消息非标准部分的格式。默认情况下,它的值为 [%thread] %logger %msgPatternLayout 可以使用的任何值都是正确的 suffixPattern 值。
stackTracePattern String 该属性允许定制出现在每个堆栈行前面的字符。默认的值为 "\t",即制表符。PatternLayout 可以使用的任何值都是正确的 stackTracePattern 值。
throwableExcluded boolean 设置该属性的值为 true,会导致堆栈信息被忽略。默认为 false,所以堆栈信息可以被发送给 syslog 服务器。

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

因为 syslog 请求的格式非常严格,所以 SyslogAppender 没有任何 layout。但是使用 suffixPattern 可以让用户展示他想展示的信息。

下面为 SyslogAppender 的配置示例:

Example: logback-syslog.xml

<configuration>

  <appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
    <syslogHost>remote_home</syslogHost>
    <facility>AUTH</facility>
    <suffixPattern>[%thread] %logger %msg</suffixPattern>
  </appender>

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

在对这个配置进行测试的时候,应该先验证远程 syslog 守护线程是否能够接收外部的资源。根据以往的经验,syslog 守护进程通常会拒绝来自网络的请求。

SiftingAppender

如名字所示,SiftingAppender 根据给定的运行时属性分离或者过滤日志。例如,SiftingAppender 可以根据用户的 session 分离日志,因此不同的用户的日志会有不同的日志文件,一个用户一个日志文件。

属性名 类型 描述
timeout Duration 一个内置的 appender 如果在指定 timeout 时间内没有被访问,则被认为是过时的。一个过时的 appender 会被关闭,并且不会被 SiftingAppende 所引用。默认值为 30 分钟
maxAppenderCount integer SiftingAppender 可以创建并且跟踪内置 appender 的最大数量。默认值为 Integer.MAX_VALUE

SiftingAppender 通过动态创建来实现这个。SiftingAppender 通过配置文件中指定的模板 (通过闭合的 <sift> 元素,见下面的例子) 来创建内置的 appender。SiftingAppender 负责管理子 appender 的生命周期。例如,SiftingAppender 会自动关闭并移除任何过时的 appender。在指定的 timeout 时间内没有被访问过的内置 appender,被认为是过时的。

在处理一个日志事件时,SiftingAppender 会委托一个子 appender 去进行处理。选择的标准是通过 discriminator 在运行时计算。用户也可以通过 Discriminator 来指定一个选择标准。让我们通过一个示例来学习一下。

示例

SiftExample 应用通过打印日志来表明应用已经启动。通过 MDC 设置键 "userid" 对应的值为 "Alice",并打印了一条日志信息。下面是主要的代码:

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

SiftingAppender 在配置文件中使用模板的示例如下:

Example: byUserid.xml

<configuration>
    
    <property name="FILE_NAME" value="FILE" />

    <appender name="SIFT"
        class="ch.qos.logback.classic.sift.SiftingAppender">
        <!-- 在缺少 class 属性的情况下,默认的 discriminator 类型为                                          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}_${FILE_NAME}.log</file>
                <append>false</append>
                <layout class="ch.qos.logback.classic.PatternLayout">
                    <pattern>%d [%thread] %level %mdc %logger{35} - %msg%n</pattern>
                </layout>
            </appender>
        </sift>
    </appender>

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

在没有 class 属性的情况下,默认的 discriminator 类型为 MDCBasedDiscriminator。discriminator 的的值为 MDC 的 key 所对应的值。但是,如果 MDC 的值为 null,那么 defaultValue 的将为 discriminator 的值。

SiftingAppender 的独特之处在于它有能力去引用以及配置子 appender。在上面的例子中,SiftingAppender 会创建多个 FileAppender 实例。每个 FileAppender 实例通过 MDC 的 key 所对应的值来标识。每当 MDC 的 key "userid" 被分配一个新值时,一个新的 FileAppender 将会被构建。SiftingAppender 可以追踪它所创建的 appender。appender 在 30 分钟之内没有被使用将会被自动关闭并丢弃。

导出变量 有不同 appender 实例是不够的。每一个实例都必须输出到一个唯一的资源中。为了做到这种区分,在 appender 模板中,key 被传递给 discriminator。在上面的例子中是 "userid",它将被导出并变成一个变量。因此,该变量可以通过给定的子 appender 来区分具体的资源。

在上面的示例中,使用 "byUserid.xml" 来运行 SiftExample,将会创建两个不同的日志文件,"unknown.log" 与 "Alice.log"。

本地变量 在版本 1.0.12 中,配置文件中局部变量的属性也可以应用到内置的 appender 中。而且,你可以在 <sift> 元素中定义变量以及动态定义属性。或者在 <sift> 元素之外定义变量,在里面使用也是支持的。

获取正确的 timeout

对于特定类型的应用,正确的获取 timeout 参数非常困难。如果 timeout 过小,一个新的内置 appender 在创建几秒钟之后就被移除了。这种现象被称为 "制造垃圾"。如果 timeout 的值过大,那么 appender 会快速接连的被创建,可能会耗尽资源。同理,设置 maxAppenderCount 的值太低会产生垃圾。

在大多数情况下,在代码中显示的指出不需要再创建内置的 appender。需要在代码中标记日志事件为 FINALIZE_SESSION。无论什么时候 SiftingAppender 看到日志事件标记为 FINALIZE_SESSION,它将会终结相关的子 appender。在生命周期快结束时,内置的 appender 将会留存几秒钟来处理之后到来的日志事件,然后再关闭。

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.");

    ... do whather the job needs to do
    
    // 将导致内置 appender 结束生命周期。但是会留存几秒钟
    logger.info(FINALIZE_SESSION_MARKER, "About to end the job");

    try {
      .. perform clean up
    } catch(Exception e);  
      // 被留存的 appender 处理,但是不会再创建新的 appender
      logger.error("unexpected error while cleaning up", e);
    }
  }

AsyncAppender

AsyncAppender 异步的打印 ILoggingEvent。它仅仅是作为一个事件调度器的存在,因此必须调用其它的 appender 来完成操作。

默认满了 80% 会丢弃数据 AsyncAppender 使用 BlockingQueue 来缓存日志时间。AsyncAppender 会创建一个工作线程去队列的头部获取数据,并将日志事件调度给附加再 AsyncAppender 上的 appender。在默认的情况下,在队列被占用了 80% 的情况下,AsyncAppender 会丢弃掉级别为 TRACE,DEBUG,INFO 的日志事件。这个策略虽然会丢失掉日志,但是对性能有利。

停止/重新部署应用 当停止或者重新部署应用时,AsyncAppender 必须被终止,为了停止并召回工作线程,以及刷新队列中的日志事件。通过终止上下文可以达到这个目标,并会关闭所有的 appender,包含所有 AsyncAppender 实例。AsyncAppender 将等待工作线程指定的 maxFlushTime 时间来刷新队列。如果你发现在关闭 LoggerContext 期间,队列中的日志事件被丢弃了,那么你需要去增加 maxFlushTime。指定 maxFlushTime 的值为 0 将会强制 AsyncAppender 等待所有日志事件被刷新才会从 stop() 方法中返回。

停止后再清理 取决于 JVM 的停止模式,工作线程在处理队列中的事件时被打断会导致日志事件在队列中被阻塞。一般发生在 LoggerContext 没有被完全停止,或者 JVM 在典型控制流之外终止。为了避免工作队列在这些情况下被打断。将一个 shutdown hook 在 JVM 运行时插入,在 JVM 开始准备停止的时候可以正确的关闭 LoggerContext。当其它的 shutdown hook 尝试记录事件时,shutdown hook 可以作为首选的方法来完全关闭 logback。

最后一句话没有搞懂是什么意思

如下是 AsyncAppender 的一些属性:

属性名 类型 描述
queueSize int 队列的最大容量,默认为 256
discardingThreshold int 默认,当队列还剩余 20% 的容量时,会丢弃级别为 TRACE, DEBUG 与 INFO 的日志,仅仅只保留 WARN 与 ERROR 级别的日志。想要保留所有的事件,可以设置为 0
includeCallerData boolean 获取调用者的数据相对来说比较昂贵。为了提高性能,默认情况下不会获取调用者的信息。默认情况下,只有像线程名或者 MDC 这种"便宜"的数据会被复制。设置为 true 时,appender 会包含调用者的信息
maxFlushTime int 根据所引用 appender 队列的深度以及延迟, AsyncAppender 可能会耗费长时间去刷新队列。当 LoggerContext 被停止时, AsyncAppender stop 方法会等待工作线程指定的时间来完成。使用 maxFlushTime 来指定最大的刷新时间,单位为毫秒。在指定时间内没有被处理完的事件将会被丢弃。这个属性的值的含义与 Thread.join(long) 相同
neverBlock boolean 默认为 false,在队列满的时候 appender 会阻塞而不是丢弃信息。设置为 true,appender 不会阻塞你的应用而会将消息丢弃

默认情况下,事件队列的最大 容量为 256。如果队列被填满,那么新的日志事件将被阻塞,直到工作线程有机会去调度日志事件。当队列的容量没有处在最大容量的时候,应用线程能够再次开始记录日志。所以,在 AsyncAppender 在缓冲区的容量满了或者快满的情况下,异步日志记录变成了伪异步。但这也不是什么坏的事。虽然在 appender 缓冲区的压力减少之前,会稍微花点时间去处理日志事件,但是这个设计可以让应用继续保持运行。

为应用的最大吞吐量优化 appender 的事件队列依赖以下几个因素。以下的任何一个因素都有可能导致伪同步的发生:

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

为了保持事物继续下去,增加队列的大小通常有用,但是是以应用可用的堆为代价。

丢弃行为 根据之前的讨论,为了减少阻塞。默认情况下,当剩余容量少于 20% 的时候,AsyncAppender 会丢掉 TRACE, DEBUG 以及 INFO 级别的日志,保留 WARN 与 ERROR 级别的日志。该策略可以确保非阻塞的处理日志事件 (因此具有高性能)。通过设置 discardingThreshold 的值为 0 可以阻止丢弃日志事件。

Example: logback-async.xml

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>%logger{35} - %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>

编写你自己的 Appender

通过继承 AppenderBase 可以编写你自己的 appender。它支持处理过滤器,状态信息,以及其它大多数 appender 共享的功能。子类仅仅只需要实现 append(Object eventObject) 方法。

接下来列出来的 CountingConsoleAppender,限制了输出到控制台的日志事件的数量。当日志事件的数量达到上限时,它会退出。它使用 PatternLayoutEncoder 来格式化日志事件,还可以接收一个 limit 的参数。因此,除了 append(Object eventObject) 方法之后,还需要一些其它的方法。正如下面展示的,这些参数都是通过 logback 多种配置机制来自动处理的。

Example: CountingConsoleAppender.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;
    }
    // 通过我们自己的 layout 来格式化日志事件
    try {
      this.encoder.doEncode(event);
    } catch (IOException e) {
    }

    // 准备下一个事件
    counter++;
  }

  public PatternLayoutEncoder getEncoder() {
    return encoder;
  }

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

start() 方法会检查是否有 PatternLayoutEncoder 存在,如果不存在,appender 将会启动失败并会发出错误信息。

这个定制的 appender 说明了两点:

  • 所有的属性遵循 JavaBean 的 setter/getter 转换,由 logback 透明的处理。在 logback 配置期间,start() 方法会被自动调用,用来验证 appender 的各种属性是否设置与一致
  • AppenderBase.doAppend() 方法会调用它子类的所有 append() 方法。实际上输出操作发生在 append() 方法中。而且,在这个方法中,appender 通过调用它们的 layout 来格式日志事件。

CountingConsoleAppender 可以像其它的 appender 一样配置。详情见 countingConsole.xml

Logback Access

大部分的 appender 都可以在 logback-classic 中找到,同样的在 logback-access 中也可以找到。它们的工作本质上与在 logback-classic 中表现的是一样的。在接下来的部分,我们将讨论它们的用法。

SocketAppender 与 SSLSocketAppender

SocketAppender 被委托将序列化的 AccessEvent 对象记录到远程实体上去。远程日志事件对 access event 来说是非侵入式的。在接收到并序列化之后,日志事件就像在本地被生成一样。

SSLSocketAppender 扩展了 SocketAppender,通过 SSL 传输日志到远程实体上。

access 的 SocketAppender 属性跟 classic 中的 SocketAppender 属性一样。

ServerSocketAppender 与 SSLServerSocketAppender

SocketAppender 一样,ServerSocketAppender 被委托传输序列化后的 AccessEvent 对象到远程实体上。但是,使用 ServerSocketAppender 时,appender 充当一个服务器的角色,被动的监听 TCP 端口,等待客户端的连接。传送到 appender 的日志事件将被分发给所有连接的客户端。

SSLServerSocketAppender 拓展了 ServerSocketAppender,通过 SSL 传输日志到远程实体上。

access 的 ServerSocketAppender 属性跟 classic 中的 ServerSocketAppender 属性一样。

SMTPAppender

access 中的 SMTPAppender 工作的机制跟 classic 中的一样。但是 evaluator 属性完全不同。默认情况下,SMTPAppender 使用一个 URLEvaluator 对象。这个 evaluator 包含了一个 url 列表,用来检查当前请求的 url。当其中一个页面给 URLEvaluator 进行请求时,SMTPAppender 会发送一封邮件。

下面是在 access 环境下的一个例子:

Example: logback-smtp.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>[email protected]</from>
  <smtpHost>mail.domain.com</smtpHost>
  <to>[email protected]</to>
</appender>

在某些特定的流程中,用户选择的页面是一个重要的步骤,那么将会触发邮件的发送。例如,当一个这样的页面被访问时,之前被访问过的页面会包含在邮件中被发送,还会包含任何用户想要的信息。

DBAppender

DBAppender 用来将 access 事件插入到数据库。

DBAppender 用到两张表:access_event 以及 access_event_header。在使用 DBAppender 之前,它们必须存在。logback 内置了 SQL 脚本用来创建表格。它们在 logback-access/src/main/java/ch/qos/logback/access/db/script 文件夹中。大部分流行的数据库都有一个对应的脚本。如果你使用的数据库不存在一个这样的脚本,那么你可以根据已经存在例子,很轻易的就可以写一个。我们鼓励你将一个这样的脚本提交到这个项目中。

access_event 包含的字段如下:

字段 类型 描述
timestamp big int access 时间创建的时间
requestURI varchar 请求的 URI
requestURL varchar 请求的 URL。由请求方法,请求 URI 以及请求协议组成
remoteHost varchar 远程主机的名字
remoteUser varchar 远程用户的名字
remoteAddr varchar 远程 IP 地址
protocol varchar 请求协议。例如 HTTPHTTPS
method varchar 请求方法。通常为 GETPOST
serverName varchar 发出请求的服务器的名字
event_id int access 事件的数据库 id

access_event_header 包含了每个请求头。字段如下:

字段 类型 描述
event_id int 相对应 access 事件的数据库 id
header_key varchar 请求头的名字,例如 User-Agent
header_value varchar 请求头的值,例如 Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) Gecko/20061010 Firefox/2.0

classic 中的 DBAppender 属性在 access 的 DBAppender 中一样有效。后者提供了另一个选项,如下:

属性名 类型 描述
insertHeaders boolean 告诉 DBAppender 用所有请求的请求头来填充数据库

下面是一个使用 DBAppender 的例子:

Example: logback-DB.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 中的 SiftingAppender 非常相似。主要的不同在于 logback-access 默认的 discriminator 名字叫 AccessEventDiscriminator,而不是基于 MDC。从名字可以看出,AccessEventDiscriminator 在 AccessEvent 中使用一个指定的字段来选择一个内置的 appender。如果它的值为 null,那么将使用 defaultValue 指定的值。

指定的 AccessEvent 可以是 COOKIE, REQUEST_ATTRIBUTE, SESSION_ATTRIBUTE, REMOTE_ADDRESS, LOCAL_PORT, REQUEST_URI 其中的一种。注意,前三个字段中必须指定 AdditionalKey

下面是配置示例:

Example: access-siftingFile.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.0-alpha4" class="ch.qos.logback.core.FileAppender">
        <file>byUser/ch.qos.logback:logback-site:jar:1.3.0-alpha4.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 实例。默认的 discriminator,名叫 AccessEventDiscriminator,会在每个 AccessEvent 中查找一个 "username" 的 session 属性。如果没有,那么将使用默认值 "NA"。因此,如果一个名叫 "username" 的 session 属性包含了用户每条日志的用户名,那么以用户名命名的日志文件将会在 byUser/ 文件夹下,日志文件包含了该用户产生的所有 access 日志。

猜你喜欢

转载自blog.csdn.net/weixin_34268579/article/details/87425529