第四章:Appenders

第四章:Appenders

什么是 Appender

logback 将写入日志事件的任务委托给一个名为 appender 的组件。Appender 必须实现 ch.qos.logback.core.Appender 接口。该接口的方法如下:
1
package ch.qos.logback.core;
2
3
import ch.qos.logback.core.spi.ContextAware;
4
import ch.qos.logback.core.spi.FilterAttachable;
5
import ch.qos.logback.core.spi.LifeCycle;
6
7
8
public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable {
9
10
public String getName();
11
public void setName(String name);
12
void doAppend(E event);
13
}
Copied!
doAppender() 方法接收一个泛型参数 E 作为唯一的参数。E 的实际参数类型取决于 logback 模块。在 logback-classic 模块里面,E 的类型是 ILoggingEvent 。在 logback-access 模块里面,E 的类型是 AccessEventdoAppend() 是 logback 框架里面最重要的模块。它的责任是将日志事件进行格式化,然后输出到对应的设备上。
Appender 都是实体类,这样可以确保它们通过名字被引用。Appender 接口继承了 FilterAttachable 接口。使得一个或多个过滤器可以附加到 appender 实例上。
Appender 最基本的责任是将日志事件进行输出。然而,它们可以委托 Layout 或者 Encoder 对象来对日志事件进行格式化。每一个 layout/encoder 有且只与一个 appender 相关联。例如,SocketAppender 仅仅序列化日志事件,然后再通过线路传输。

AppenderBase

ch.qos.logback.core.AppenderBase 是一个抽象类,实现了 Appender 接口。它提供了基本方法供所有 appender 使用。例如:获取或设置名称的方法、激活状态、布局以及过滤器。它是 logback 中所有 appender 的父类。尽管是一个抽象类,但是 AppenderBase 还实现了 Append 接口中 doAppend() 方法。可能附上源码的摘要来讨论 AppenderBase 是最清楚的方式。
1
public synchronized void doAppend(E eventObject) {
2
3
// prevent re-entry.
4
if (guard) {
5
return;
6
}
7
8
try {
9
guard = true;
10
11
if (!this.started) {
12
if (statusRepeatCount++ < ALLOWED_REPEATS) {
13
addStatus(new WarnStatus(
14
"Attempted to append to non started appender [" + name + "].",this));
15
}
16
return;
17
}
18
19
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
20
return;
21
}
22
23
// ok, we now invoke the derived class's implementation of append
24
this.append(eventObject);
25
26
} finally {
27
guard = false;
28
}
29
}
Copied!
doAppend() 的实现是 synchronized 的。不同的线程通过同一个 appender 打印日志是线程安全的。当一个线程 T 正在执行 doAppend() 方法,接下来其它的线程调用将会被阻塞直到线程 T 离开 doAppend() 方法,这样可以确保 T 对 appender 的访问具有独占性。
因为这种同步并不总是适合的,所以 logback 提供了 ch.qos.logback.core.UnsynchronizedAppenderBase 类,0跟 AppenderBase 类十分的相似。为了简单起见,接下来的内容我们只讨论 UnsynchronizedAppenderBase
首先,doAppend() 方法会去检查 guard 是不是为 true。如果是,它会立即退出。如果 guard 不是为 true,下一步将它设置为 true。guard 会确保 doAppend() 方法不会被自身递归调用。想象一下这样的一个组件,被在 append() 方法之外的地方被调用,用于打印日志。它的调用可能被直接指向一个完全相同的刚刚调用过它的一个 appender,导致无限循环和堆栈溢出。
注:`UnsynchronizedAppenderBase 类中有一个 guard 变量
private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>();
接下来,我们会检查 started 是否为 true。如果不是,doAppend() 会发出一个警告并返回。换句话说,一旦 appender 被关闭,就不能对它进行写入。Appender 对象实现了 LifeCycle 接口,也就是说它实现了 start()stop()isStarted 方法。在设置完一个 appender 的所有属性之后,logback 的配置框架 - Joran,将会给 appender 发一个信号去激活它的属性。根据它的类型,一个 appender 可能会启动失败,如果某些特定的属性丢失或者由于各种属性之间的冲突。例如,创建文件依赖截断模式,FileAppender 不能对 File 选项的值起作用,直到这个值确定下来。明确的激活步骤可以确保一个 appender 在知道它们的值之后再作用于其属性。
如果 appender 不能被被启动或者被停止,logback 内部状态管理系统将会发出一条警告信息。在几次尝试之后,为了避免被同一条警告信息淹没内部状态系统,doAppend() 会停止发出这些警告。
接下来 if 语句检查过滤器的结果,根据过滤器链的结果,事件可以被拒绝或者被接受。如果缺少过滤器链,事件默认会被接受。
接下来 doAppend() 调用 append() 的实现方法。这个方法是实际的执行者,用来将事件附加到合适的设备上。
最后,guard 被释放,后续对 append() 的调用得以执行。
在手册的其余部分,我们保留 optionproperty,用于 JavaBean 通过 get 和 set 方法动态推断出来的任何属性。

Logback-core

Logback-core 为 logback 其他模块的构建奠定了基础。一般来说,logback-core 的组件需要一些定制,尽管很少。但是,在接下来的几个部分,我们描述了一种可以开箱即用的 appender。

OutputStreamAppender

OutputStreamAppender 将事件附加到 java.io.OutputStream 上。这个类提供了其它 appender 构建的基础服务。用户通常不会直接实例一个 OutputStreamAppender 实例。因为一般来说 java.io.OutputStream 类型不能方便的转为 String。因为在配置文件中没有方法去直接指定一个 OutputStream 目标对象。简单来说,你不能通过配置文件配置一个 OutputStreamAppender。但是这并不意味着 OutputStreamAppender 缺少配置属性。这些属性描述如下:
属性名
属性值
描述
ecoder
Encoder
决定通过哪种方式将事件写入 OutputStreamAppender,Encoder 将会在单独的章节介绍
immediateFlush
boolean
immediateFlush 的默认值为 true。立即刷新输出流可以确保日志事件被立即写入,并且可以保证一旦你的应用没有正确关闭 appender,日志事件也不会丢失。从另一方面来说,设置这个属性为 false,有可能会使日志的吞吐量翻两番(视情况而定)。但是,设置为 false,当应用退出的时候没有正确关闭 appender,会导致日志事件没有被写入磁盘,可能会丢失。
OutputStreamAppender 是其他三个 appender 的父类,分别是 ConsoleAppenderFileAppender 以及 RollingFileAppenderFileAppender 又是 RollingFileAppender 的父类。下面的类图展示 OutputStreamAppender 与子类之间的关系:
appenderClassDiagram

ConsoleAppender

ConsoleAppender 就跟名字显示的一样,是将日志事件附加到控制台,跟进一步说就是通过 System.out 或者 System.err 来进行输出。默认通过前者。ConsoleAppender 通过用户指定的 encoder,格式化日志事件。Encoder 会在接下来的章节讨论。System.outSystem.err 两者都是 java.io.PrintStream 类型。因此,它们被包装在可以进行 I/O 缓存操作的 OutputStreamWriter 中。
属性名
类型
描述
encoder
Encoder
OutputStreamAppender 属性
target
String
System.outSystem.err。默认为 System.out
withJansi
boolean
withJansi 的默认值为 false。设置 withJansitrue 可以激活 Jansi 在 windows 使用 ANSI 彩色代码。在 windows 上如果设置为 true,你应该将 org.fusesource.jansi:jansi:1.9 这个 jar 包放到 classpath 下。基于 Unix 实现的操作系统,像 Linux、Max OS X 都默认支持 ANSI 才彩色代码。
Example: ConsoleAppender configuraion (logback-Console.xml)
1
<configuration>
2
3
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" >
4
<!-- encoder 默认使用 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
5
<encoder>
6
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
7
</encoder>
8
</appender>
9
10
<root level="DEBUG">
11
<appender-ref ref="STDOUT" />
12
</root>
13
</configuration>
Copied!
通过以下命令执行上面的配置文件:
1
java chapters.appenders.ConfigurationTester logback-Console.xml
Copied!

FileAppender

FileAppenderOutputStreamAppender 的子类,将日志事件输出到文件中。通过 file 来指定目标文件。如果该文件存在,根据 append 的值,要么将日志追加到文件中,要么该文件被截断。
属性名
类型
描述
append
boolean
如果为 true,日志事件会被追加到文件中,否则的话,文件会被截断。默认为 true
encoder
Encoder
参见 OutputStreamAppender 的属性
file
String
要写入文件的名称。如果文件不存在,则新建。在 windows 平台上,用户经常忘记对反斜杠进行转义。例如,c:\temp\test.log 不会被正确解析,因为 '\t' 是一个转义字符,会被解析为一个 tab 字符 (\u0009)。正确的值应该像:c:/temp/test.log 或者 c:\\temp\\test.log。没有默认值。
prudent
boolean
在严格模式下,FileAppender 会将日志安全的写入指定文件。即使在不同的 JVM 或者不同的主机上运行 FileAppender 实例。默认的值为 false。 严格模式可以与 RollingFileAppender 结合使用。 严格模式也意味着 append 属性被自动设置为 true。 严格模式依赖排他文件锁。实验证明,文件锁大概是写入日志事件成本的 3 倍。在严格模式关闭的情况下,往一台"普通"电脑的硬盘上将一个日志事件写入文件,大概需要耗费 10 微秒。但是在开启的情况下,大概需要 30 微秒。也就是说在关闭的情况下可以一秒钟写入 100'000 个日志事件,但是在开启的情况下,一秒钟只能写入33'000 个日志事件。 严格模式可以在所有 JVM 写入同一个文件时,有效的序列化 I/O 操作。因此,随着竞相访问同一个文件的 JVM 数量上升,将会延迟每一个 I/O 操作。只要总共的 I/O 操作大约为每秒 20 个日志请求,对性能的影响可以被忽略。但是,如果应用每秒产生了 100 个以上的 I/O 操作,性能会受到明显的影响,应该避免使用严格模式。 网络文件锁 当日志文件位于网络文件系统上时,严谨模式的成本会更高。同样重要的是,网络文件系统的文件锁带有很强的偏向性,当前获得锁的进程在释放锁之后会立马又重新获得。因此,当一个进程独占日志文件,将会导致其它进程饥饿死锁。 严格模式的影响严重依赖网速以及操作系统实现的细节。我们提供了一个小型应用 FileLockSimulator 用于在你的环境中模拟严格模式。
立即刷新 默认情况下,每一个日志事件都会被立即刷新到底层的输出流。默认方法更加的安全,因为日志事件在你的应用没有正确关闭 appender 的情况下不会丢失。但是,要想显著的增加日志的吞吐率,你可以将 immediateFlush 设置为 false
下面是 FileAppender 的配置示例:
Example: logback-fileAppender.xml
1
<configuration>
2
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
3
<file>testFile.log</file>
4
<!-- 将 immediateFlush 设置为 false 可以获得更高的日志吞吐量 -->
5
<immediateFlush>true</immediateFlush>
6
<!-- 默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
7
<encoder>
8
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
9
</encoder>
10
</appender>
11
12
<root level="DEBUG">
13
<appender-ref ref="FILE" />
14
</root>
15
</configuration>
Copied!
logback-examples 的文件夹下,运行以下命令:
1
java chapters.appenders.ConfigurationTester logback-fileAppender.xml
Copied!
要指定配置文件的具体路径
也可以直接在 eclipse 里面 Run Application 时设置 Arguments

文件唯一命名 (使用时间戳)

在应用的开发阶段或者短期应用中,例如:批处理程序,在每个应用启动的时候创建一个新的日志文件。通过 <timestamp> 元素可以轻易做到这点。
Example: logback-timestamp.xml
1
<configuration>
2
<!-- 通过 "bySecond" 将时间格式化成 "yyyyMMdd'T'HHmmss" 的形式插入到 logger 的上下文中
3
这个值对后续的配置也适用
4
-->
5
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss" />
6
7
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
8
<!-- 利用之前创建的 timestamp 来创建唯一的文件 -->
9
<file>log-${bySecond}.txt</file>
10
<encoder>
11
<pattern>%logger{35} - %msg%n</pattern>
12
</encoder>
13
</appender>
14
15
<root level="DEBUG">
16
<appender-ref ref="FILE" />
17
</root>
18
</configuration>
Copied!
timestamp 元素需要两个强制的属性 keydatePattern 以及可选的属性 timeReferencekey 属性的值是来区分哪个 timestamp 元素,并且在后续的配置中可以通过TODO 变量替换来使用。datePattern 属性用于将当前时间格式化成字符串。日期格式必须遵循 SimpleDateFormat 中的规范。timeReference 表示时间戳引用哪个时间。默认为解析配置文件的时间,也就是当前时间。但是,在一些特定的情况下,可以设置为上下文初始化的时间。通过 设置 timeReference 的值为 contextBirth
通过一下命令来测试 <timestamp> 元素:
1
java chapters.appenders.ConfigurationTester logback-timestamp.xml
Copied!
译者注:需要指定具体配置文件的具体路径,也可以通过 eclipse 来运行。后续不再重复此注意事项。
设置 timeReference 的值为 "contextBirth" 的例子如下:
Example: logback-timestamp-contextBirth.xml
1
<configuration>
2
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss" timeReference="contextBirth"/>
3
...
4
</configuration>
Copied!

RollingFileAppender

RollingFileAppender 继承自FileAppender,具有轮转日志文件的功能。例如,RollingFileAppender 将日志输出到 log.txt 文件,在满足了特定的条件之后,将日志输出到另外一个文件。
RollingFileAppender 进行交互的有两个重要的子组件。第一个是 RollingPolicy,它负责日志轮转的功能。另一个是 TriggeringPolicy,它负责日志轮转的时机。所以 RollingPolicy 负责发生什么,TriggeringPolicy 负责什么时候发生。
为了让 RollingFileAppender 生效,必须同时设置 RollingPolicyTriggeringPolicy。但是,如果 RollingPolicy 也实现了 TriggeringPolicy 接口,那么只需要设置前一个就好了。
RollingFileAppender 的属性如下所示:
属性名
类型
描述
file
String
参见 FileAppender
append
boolean
参见 FileAppender
encoder
Encoder
参见 OutputStreamAppender
rollingPolicy
RollingPolicy
当轮转发生时,指定 RollingFileAppender 的行为。下面将会详细说明
triggeringPolicy
TriggeringPolicy
告诉 RollingFileAppender 什么时候发生轮转行为。下面将会详细说明
prudent
boolean
FixedWindowRollingPolicy 不支持该属性。 RollingFileAppender 在使用严格模式时要与 `TimeBasedRollingPolicy 结合使用,但是有两个限制: 1. 在严格模式下,也不支持也不允许文件压缩(我们不能让一个 JVM 在写入文件时,另一个 JVM 在压缩该文件) 2. 不能对 FileAppenderfile 属性进行设置。实际上,大多数的操作系统不允许在有进程操作文件的情况下对文件改名。 其它的参考 FileAppender

Rolling policy 简介

RollingPolicy 负责轮转的方式为:移动文件以及对文件改名。
RollingPolicy 接口如下:
1
package ch.qos.logback.core.rolling;
2
3
import ch.qos.logback.core.FileAppender;
4
import ch.qos.logback.core.spi.LifeCycle;
5
6
public interface RollingPolicy extends LifeCycle {
7
8
public void rollover() throws RolloverFailure;
9
public String getActiveFileName();
10
public CompressionMode getCompressionMode();
11
public void setParent(FileAppender appender);
12
}
Copied!
rollover 方法负责对日志文件进行归档。getActiveFileName() 方法负责获取当前日志文件的名字。getCompressionMode 方法决定采取哪种压缩模式。通过 setParent 方法引用父类。
TimeBasedRollingPolicy
TimeBasedRollingPolicy 是最常用的轮转策略。它是基于时间来定义轮转策略。例如按天或者按月。TimeBasedRollingPolicy 既负责轮转的行为,也负责触发轮转。实际上,TimeBasedRollingPolicy 同时实现了 RollingPolicyTriggeringPolicy 接口。
TimeBasedRollingPolicy 的配置需要一个强制的属性 fileNamePattern 以及其它的可选属性。
属性名
类型
描述
fileNamePattern
String
该属性定义了轮转时的属性名。它的值应该由文件名加上一个 %d 的占位符。%d 应该包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略掉这个日期格式,那么就默认为 yyyy-MM-dd。轮转周期是通过 fileNamePattern 推断出来的。 注意:可以选择对 RollingFileAppenderTimeBasedRollingPolicy 的父类)中的 file 属性进行设置,也可以忽略。通过设置 FileAppenderfile 属性,你可以将当前活动日志的路径与归档日志的路径分隔开来。当前日志永远会是通过 file 指定的文件。它的名字不会随着时间的推移而发生变化。但是,如果你选择忽略 file 属性,当前活动日志在每个周期内将会根据 fileNamePattern 的值变化。稍后的例子将会说明这一点。 %d{} 中的日期格式将会遵循 java.text.SimpleDateFormat 中的约定。斜杆 '/' 或者反斜杠 '\' 都会被解析成目录分隔符。 指定多个 %d 可以指定多个 %d,但是只能有一个是主要的,用于推断轮转周期。其它的 %d 占位符必须通过 'aux' 标记为辅助的。见下面的示例: 多个 %d 占位符允许你在文件夹中去管理归档文件,这个跟轮转周期不同。如下所示:通过年月来管理日志文件夹,但是轮转周期是在每天晚上零点。 /var/log/%d{yyyy/MM, aux}/myapplication.%d{yyyy-MM-dd}.log TimeZone 在某些情况下,你可能想要根据时区而不是主机的时钟来轮转日志。你可以通过如下方式来指定一个时区,例如: aFloder/test.%d{yyyy-MM-dd-HH, UTC}.log 如果指定的 timezone 不能被识别或者拼写错误,将会根据 [TimeZone.getTimeZone(String)](http://docs.oracle.com/javase/6/docs/api/java/util/TimeZone.html#getTimeZone(java.lang.String)) 方法指定为 GMT。
maxHistory
int
这个可选的属性用来控制最多保留多少数量的归档文件,将会异步删除旧的文件。比如,你指定按月轮转,指定 maxHistory = 6,那么 6 个月内的归档文件将会保留在文件夹内,大于 6 个月的将会被删除。注意:当旧的归档文件被移除时,当初用来保存这些日志归档文件的文件夹也会在适当的时候被移除。
totalSizeCap
int
这个可选属性用来控制所有归档文件总的大小。当达到这个大小后,旧的归档文件将会被异步的删除。使用这个属性时还需要设置 maxHistory 属性。而且,maxHistory 将会被作为第一条件,该属性作为第二条件。
cleanHistoryOnStart
boolean
如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为 false。 归档文件的删除通常在轮转期间执行。但是,有些应用的存活时间可能等不到轮转触发。对于这种短期应用,可以通过设置该属性为 true,在 appender 启动的时候执行删除操作。
下面是关于 fileNamePattern 的介绍。
fileNamePattern
轮转周期
示例
/wombat/foo.%d
每天轮转(晚上零点)。由于省略了指定 %d 的日期格式,所以默认为 yyyy-MM-dd
没有设置 file 属性:在 2006.11.23 这一天的日志都会输出到 /wombat/foo.2006-11-23 这个文件。晚上零点以后,日志将会输出到 wombat/foo.2016-11-24 这个文件。 设置 file 的值为 /wombat/foo.txt:在 2016.11.23 这一天的日志将会输出到 /wombat/foo.txt 这个文件。在晚上零点的时候,foo.txt 将会被改名为 /wombat/foo.2016-11-23。然后将创建一个新的 foo.txt,11.24 号这一天的日志将会输出到这个新的文件中。
/wombat/%d{yyyy/MM}/foo.txt
每个月开始的时候轮转
没有设置 file 属性:在 2016.10 这一个月中的日志将会输出到 /wombat/2006/10/foo.txt。在 10.31 晚上凌晨以后,11 月份的日志将会被输出到 /wombat/2006/11/foo.txt。 设置 file 的值为 /wombat/foo.txt:在 2016.10,这个月份的日志都会输出到 /wombat/foo.txt。在 10.31 晚上零点的时候,/wombat/foo.txt 将会被重命名为 /wombat/2006/10/foo.txt,并会创建一个新的文件 /wombat/foo.txt ,11 月份的日志将会输出到这个文件。依此类推。
/wombat/foo.%d{yyyy-ww}.log
每周的第一天(取决于时区)
每次轮转发生在每周的第一天,其它的跟上一个例子类似
/wombat/foo%d{yyyy-MM-dd_HH}.log
每小时轮转
跟之前的例子类似
/wombat/foo%d{yyyy-MM-dd_HH-mm}.log
每分钟轮转
跟之前的例子类似
/wombat/foo%d{yyyy-MM-dd_HH-mm, UTC}.log
每分钟轮转
跟之前的例子类似,不过时间格式是 UTC
/foo/%d{yyyy-MM, aux}/%d.log
每天轮转。归档文件在包含年月的文件夹下
第一个 %d 被辅助标记。第二个 %d 为主要标记,但是日期格式省略了。因此,轮转周期为每天(由第二个 %d 控制),文件夹的名字依赖年与月。例如,在 2016.11 的时候,所有的归档文件都会在 /foo/2006-11/ 文件夹下,如:/foo/2006-11/2006-11-14.log
任何斜杆或者反斜杠够会被当作文件夹分隔符。任何必要的文件夹都会在有需要的时候创建。你可以轻松的将日志文件放在单独的文件夹中。
TimeBasedRollingPolicy 支持文件自动压缩。如果 fileNamePattern.gz 或者 .zip 结尾,将会启动这个特性。
fileNamePattern
轮转周期
示例
/wombat/foo.%d.gz
每天轮转(晚上零点),自动将归档文件压缩成 GZIP 格式
file 属性没有设置:在 2009.11.23,日志将会被输出到 /wombat/foo.2009-11-23 这个文件。但是,在晚上零点的时候,文件将会被压缩成 /wombat/foo.2009-11-23.gz。在 11.24,这一天的日志将会被直接输出到 /wombat/folder/foo.2009-11-24 这个文件。 file 属性的值设置为 /wombat/foo.txt:在 2009.11.23,日志将会被输出到 /wombat/foo.txt 这个文件。在晚上零点的时候,该文件会被压缩成 /wombat/foo.2009-11-23.gz。并会创建一个新的 /wombat/foo.txt 文件,11.24 这一天的日志将会被输出到该文件。依此类推。
fileNamePattern 有两个目的。logback 通过该属性可以进行周期性的轮转并且得到每个归档文件的名字。注意,两种跟不同的 pattern 可能会有相同的轮转周期。yyyy-MM[email protected] 同样都是按月轮转,但是归档文件最终的名字不一样。
通过设置 file 属性,你可以将活动日志文件的路径与归档文件的路径分隔开来。日志将会一直输出到通过 file 属性指定的文件中,并且不会随着时间而改变。但是,如果你选择忽略 file 属性,活动日志的名字将会根据 fileNamePattern 的值在每个周期内变化。不设置 file 属性的时候,如果在轮转期间存在外部文件句柄引用日志文件,将会避免命名错误
maxHistory 控制归档文件保留的最大数目,并删除旧的文件。例如,如果你指定按月轮转,并设定 maxHistory 的值为 6,那么 6 个月之内的归档文件都会被保留,大于 6 个月的文件将会被删除。注意,当旧的文件被移除时,为文件归档而创建的文件夹在适当的时候也会被移除。
由于各种技术原因,轮转并不是时间驱动的,而是依赖日志事件。例如,在 2002.03.08,假设 fileNamePattern 的值为 yyyy-MM-dd(按天轮转),在晚上零点之后,没有日志事件到来,假设在 23 分 47 秒之后,第一个到达的日志事件将会触发轮转。也就是说轮转实际发生在 03.09 00:23'47 AM 而不是 0:00 AM。因此,依赖日志事件的到达速度,所以轮转可能会有延迟。但是,不管延迟的情况是什么样,一定周期内生成的日志事件将会被输出到指定的文件中,从这个角度来看,轮转算法始终都会是正确的。
下面是 RollingFileAppenderTimeBaseRollingPolicy 结合使用的例子:
Example: logback-RollingTimeBased.xml
1
<configuration>
2
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
3
<file>logFile.log</file>
4
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
5
<!-- 按天轮转 -->
6
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
7
<!-- 保存 30 天的历史记录,最大大小为 30GB -->
8
<maxHistory>30</maxHistory>
9
<totalSizeCap>3GB</totalSizeCap>
10
</rollingPolicy>
11
12
<encoder>
13
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
14
</encoder>
15
</appender>
16
17
<root level="DEBUG">
18
<appender-ref ref="FILE" />
19
</root>
20
</configuration>
Copied!
下面是在 prudent 模式下(严格模式)RollingFileAppenderTimeBasedRollingPolicy 的结合使用的例子:
Example: logback-PrudentTimeBasedRolling.xml
1
<configuration>
2
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
3
<!-- 支持多个 JVM 同时写一个文件 -->
4
<prudent>true</prudent>
5
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
6
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
7
<maxHistory>30</maxHistory>
8
<totalSizeCap>3GB</totalSizeCap>
9
</rollingPolicy>
10
11
<encoder>
12
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
13
</encoder>
14
</appender>
15
16
<root level="DEBUG">
17
<appender-ref ref="FILE" />
18
</root>
19
</configuration>
Copied!

基于大小以及时间的轮转策略

有时你希望按时轮转,但同时又想限制每个日志文件的大小。特别是如果后期处理工具需要对日志进行大小限制。为了满足这个需求,logback 配备了 SizeAndTimeBasedRollingPolicy
注意,TimeBasedRollingPolicy 可以限制归档文件总的大小。所以如果你想要这个限制,你可以通过设置 totalSizeCap 来达到这个目的。
下面的示例展示了基于时间及大小的配置:
Example: logback-sizeAndTime.xml
1
<configuration debug="true">
2
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
3
<file>mylog.txt</file>
4
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
5
<!-- 按天轮转 -->
6
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
7
<maxFileSize>100MB</maxFileSize>
8
<maxHistory>60</maxHistory>
9
<totalSizeCap>20GB</totalSizeCap>
10
</rollingPolicy>
11
12
<encoder>
13
<pattern>%msg%n</pattern>
14
</encoder>
15
</appender>
16
17
<root level="DEBUG">
18
<appender-ref ref="ROLLING" />
19
</root>
20
</configuration>
Copied!
注意,除了 %d 之外还有 %i。这两个占位符都是强制要求的。在当前时间还没有到达周期轮转之前,日志文件达到了 maxFileSize 指定的大小,会进行归档,递增索引从 0 开始。
基于大小与时间的文件归档支持删除旧的归档文件。你需要指定 maxHistory 属性的值来保存几个周期的日志。当你的应用停止或者启动的时候,日志将会继续向正确的位置输出。即当前周期内索引最大的。
在 1.17 版本前,这个文档会提及一个叫 SizeAndTimeBasedFNATP 的组件。但是 SizeAndTimeBasedFNATP 组件只提供一个最简单的配置。我们不再提供关于 SizeAndTimeBasedFNATP 的文档。尽管这样,早期的配置文件使用 SizeAndTimeBasedFNATP 依然会运行的很好。 实际上,SizeAndTimeBasedRollingPolicy 是使用 SizeAndTimeBasedFNATP 实现的。
FixedWindowRollingPolicy
在轮转时,FixedWindowRollingPolicy 根据固定窗口算法重命名文件,具体描述如下:
filaNamePattern 表示归档文件的名字。这个属性是必须的,而且必须包含一个表示整形的占位符 i%
FixedWindowRollingPolicy 的可用属性如下:
属性名
类型
描述
minIndex
int
表示窗口索引的下界
maxIndex
int
表示窗口索引的上界
fileNamePattern
String
FixedWindowRollingPolicy 在重命名日志文件时将会根据这个属性来命名。它必须包含一个 i% 的占位符,该占位符指明了窗口索引的值应该插入的位置。 例如,当该属性的值为 MyLogFile%i.log,最小与最大的值分别为 13。将会产生的归档文件为 MyLogFile1.logMyLogFile2.logMyLogFile3.log。 文件压缩的方式也是通过该属性来指定。例如,设置该属性的值为 MyLogFile%i.log.zip,那么归档文件将会被压缩成 zip 格式。也可以选择压缩成 gz 格式。
由于窗口固定算法需要跟窗口大小一样的的重命名次数,因此强烈不推荐太大的窗口大小。当用户指定一个较大值时,当前的实现会将窗口大小自动减少为 20。
让我们通过一个例子来了解下固定窗口算法。假设 minIndex 的值为 1maxIndex 的值为 3fileNamePattern 的值为 foo%i.logfile 属性的值为 foo.log
轮转数目
当前输出文件
归档日志文件
描述
0
foo.log
-
还没有到轮转周期,logbak 将日志输出初始文件
1
foo.log
foo1.log
第一次轮转,foo.log 被重命名为 foo1.log。一个新的 foo.log 文件将会被创建并成为当前输出文件
2
foo.log
foo1.log,foo2.log
第二次轮转,foo1.log 被重命名为 foo2.logfoo.log 被重命名为 foo1.log。一个新的 foo.log 被创建并成为当前输出文件
3
foo.log
foo1.log,foo2.log,foo3.log
第三次轮转,foo2.log 被重命名为 foo3.logfoo1.log 被命名为 foo2.logfoo.log 被重命名为 foo1.log。一个新的 foo.log 被创建并成为当前输出文件
4
foo.log
foo1.log,foo2.log,foo3.log
在这次以及后续的轮转中,将会删除 foo3.log 文件,其它文件的重命名操作跟之前的步骤一样。在本次以及以后的轮转中,将会一直只有三个归档文件以及一个活跃的日志文件
下面的给出了 RollingFileAppender 配合 FixedWindowRollingPolicy 使用的例子。注意,file 属性是强制的,即使它包含了一些跟 fileNamePattern 属性相同的信息。
Example:logback-RollingFixedWindow.xml
1
<configuration>
2
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
3
<file>test.log</file>
4
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
5
<fileNamePattern>tests.%i.log.zip</fileNamePattern>
6
<minIndex>1</minIndex>
7
<maxIndex>3</maxIndex>
8
</rollingPolicy>
9
10
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
11
<maxFileSize>5MB</maxFileSize>
12
</triggeringPolicy>
13
14
<encoder>
15
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
16
</encoder>
17
</appender>
18
19
<root level="DEBUG">
20
<appender-ref ref="FILE" />
21
</root>
22
</configuration>
Copied!

触发策略简介

TriggeringPolicy 的实现用于通知 RollingFileAppender 何时轮转。
TriggeringPolicy 接口仅仅只包含了一个方法。
1
package ch.qos.logback.core.rolling;
2
3
import java.io.File;
4
import ch.qos.logback.core.spi.LifeCycle;
5
6
public interface TriggeringPolicy<E> extends LifeCycle {
7
8
public boolean isTriggeringEvent(final File activeFile, final <E> event);
9
}
Copied!
isTriggeringEvent() 方法接收当前活动的文件以及当前的日志事件作为参数。基于这些参数,通过具体的实现来决定轮转是不是应该发生。
TimeBasedRollingPolicy 是使用最广泛的触发策略。也可以用作轮转策略来使用。
SizeBasedTriggeringPolicy
SizeBasedTriggeringPolicy 观察当前活动文件的大小,如果已经大于了指定的值,它会给 RollingFileAppender 发一个信号触发对当前活动文件的轮转。
SizeBasedTriggeringPolicy 只接收 maxFileSize 这一个参数,它的默认值是 10 MB。
maxFileSize 可以为字节,千字节,兆字节,千兆字节,通过在数值后面指定一个后缀 KBMB 或者 GB。例如,50000005000KB5MB 以及 2GB 都是有效的,前三个是一样的。
1
<configuration>
2
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
3
<file>test.log</file>
4
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
5
<fileNamePattern>test.%i.log.zip</fileNamePattern>
6
<minIndex>1</minIndex>
7
<maxIndex>3</maxIndex>
8
</rollingPolicy>
9
10
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
11
<maxFileSize>5MB</maxFileSize>
12
</triggeringPolicy>
13
<encoder>
14
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n
15
</pattern>
16
</encoder>
17
</appender>
18
19
<root level="DEBUG">
20
<appender-ref ref="FILE" />
21
</root>
22
</configuration>
Copied!

Logback Classic

虽然日志事件在 logback-core 是通用的,但是在 logback-classic 中,它们永远是 ILoggingEvent 的实例。logback-classic 只不过是用来处理 ILoggingEvent 实例的专门处理管道。

SocketAppender and SSLSocketAppender

到目前为止,所介绍的 appender 只能将日志输出到本地资源。相反的是,SocketAppender 被设计成可以将 ILoggingEvent 实例序列化再传输到远端机器。当使用 SocketAppender 时,日志事件将以明文发送,使用 SSLSocketAppender 时,日志事件将通过安全的通道传输。
序列化事件的实际类型为 LoggingEventVO,它实现了 ILoggingEvent 接口。就日志事件而言,远程日志是非侵入式的。在接收到日志事件并反序列化之后,日记事件就像在本地生成的一样。多个 SocketAppender 实例运行在不同的机器上,直接将它们的日志通过固定的格式输出到中央日志服务器上。SocketAppender 不会关联 layout,因为它是序列化日志事件到远程服务器上。SocketAppenderTransmission Control Protocol (TCP) 层上运行,该层提供了可靠,有序,流式控制以及段对端的八位字节流。所以,如果远程服务器是可以到达的,那么日志事件最终都会到达那里。相反,如果远程服务器挂掉或者不可达到,那么日志事件会被丢弃。如果服务器重新恢复,那么日志事件的传输将会继续进行。这种重连是通过一个连接线程池周期性的尝试连接来进行的。
日志事件由本地 TCP 实现自动缓冲。也就是说如果连接到服务器的速度很慢,但是比客户端产生日志事件的速度要快,那么客户端不会受到网速的影响。但是,如果网络连接速度比日志产生速度要慢,那么客户端只能以网络速度进行处理。特别在极端情况下,连接到服务器的网络挂掉了,客户端最终会被阻塞。如果网络连接恢复了,但是服务器挂掉了,客户端不会阻塞,尽管因为服务器挂掉了,日志事件丢失。
尽管 SocketAppender 不再依赖任何的 logger,在当前线程的连接下也不会被垃圾收集。但是连接线程只有在服务器挂掉的情况下才存在,为了避免出现垃圾收集问题,你需要明确的关闭 SocketAppender。生命周期长的应用会创建/销毁许多 SocketAppender 实例,应该注意到这个垃圾回收问题。大部分的应用可以忽略这个问题。如果 JVM 在 SocketAppender 关闭之前退出,无论是显式的退出或者是通过后续的垃圾回收,都可能会导致未传输的数据在管道中被丢失。这是基于 windows 系统常见的问题。为了避免数据丢失,通常的做法是调用 close() 方法去关闭 SocketAppender,或者在应用退出之前调用 LoggerContextstop() 方法。
远程服务器通过 remoteHostport 属性来标识。SocketAppender 属性罗列在下表当中。SSLSocketAppender 支持额外的一些属性,将在第十五章进行讨论。
属性名
类型
描述
includeCallerData
boolean
如果为 true,那么调用者的信息也会被发送到服务端。默认为 false。
port
int
远程服务器的端口号
reconnectionDelay
Duration
接受一个表示持续时间的字符串,例如 "10 seconds" 代表每次重连的间隔时间。默认值为 30 秒。将这个值设置为 0,将会关闭重连机制。注意,如果连接服务器成功,则不会出现连接线程池。
queueSize
int
接受一个整数(大于0)代表有多少个日志事件传输到服务端。当这个值为 1 时,会同步传输日志事件到远端。当这个值大于 1 时,将设队列中还有剩余的空间,那么一个新的事件将会入队。队列的长度大于 1 可以提高性能,消除网络引起的延时。 参见 eventDelayLimist 属性。
eventDelayLimit
Duration
接受一个表示持续时间的字符串,例如:"10 seconds"。它表示一旦当前队列已满(例如已经包含了 queueSieze 个事件),在丢弃事件之前的等待时间。在远端服务器一直缓慢接收事件时,这种情况就会发生。默认值为 100 毫秒。
remoteHost
String
服务器的主机名
ssl
SSLConfiguration
只支持 SSLSocketAppender,这个属性提供了 SSL 配置供 appender 使用,将在第十五章进行讨论
日志服务器的选择
logback classic 给服务器提供了两个选择接收来自 SocketAppenderSSLSocketAppender 的日志事件。
  • ServerSocketReceiver 与跟它相对应的具有 SSL 功能的 SSLServerSocketReceiver 都是接收组件。可以通过配置应用中的 logback.xml 文件来接收远程 socket appender 的日志事件。查看第十四章获取更多的信息。
  • SimpleSocketServer 与具有 SSL 功能的 SimpleSSLSocketServer 都提供了一个简单的 java 应用程序,该应用程序被设计成可配置,并且可以在命令行界面运行。这些应用仅仅等待来自 SocketAppenderSSLSocketAppender 的日志事件,每个被接收的日志事件按照本地服务器策略进行打印。下面给出一个简单的例子。
使用 SimpleSocketServer
SimpleSocketServer 应用接收两个命令行参数:portconfigFileport 监听的端口,configFile 表示 XML 格式的配置文件。
logback-examples/ 文件夹下,通过一下命令来启动 SimpleSocketServer
1
java ch.qos.logback.classic.net.SimpleSocketServer 6000 \
2
src/main/java/chapters/appenders/socket/server1.xml
Copied!
6000 为监听的端口,在 server1.xml 中,ConsoleAppenderRollingFileAppender 被添加到 root logger 上。
在启动了 SimpleSocketServer 之后,你可以在多个客户端上使用 SocketAppender 来发送日志事件。这个手册中相关的示例包含了两个这样的客户端:chapters.appenders.SocketClient1chapters.appenders.SocketClient2。两个客户端都会等待用户在控制台输入字符。输入的字符会被包裹在 debug 级别的日志事件中,然后发送到远程服务器。这个两个客户端不同的地方在于 SocketAppender 的配置。SocketClient1 通过编码来配置,SocketClient2 需要获取一个配置文件。
SimpleSocketServer 在本地机器上启动之后,通过以下命令去连接:
1
java chapters.appenders.socket.SocketClient1 localhost 6000
Copied!
你输入的每一行字符都会出现在之前启动 SimpleSocketServer 的控制台上。如果你停止或者重启 SimpleSocketServer,客户端会重连新的服务实例,但是在断开连接时的日志时间会丢失(不能取消)。
SocketClient1 不同的是,SocketClient2 需要获取一个 XML 格式的配置文件来进行配置。配置文件 client1.xml 如下所示,它创建了一个 SocketAppender 附加到 root logger 上。
Example: client1.xml
1
<configuration>
2
3
<appender name="SOCKET" class="ch.qos.logback.classic.net.SocketAppender">
4
<remoteHost>${host}</remoteHost>
5
<port>${port}</port>
6
<reconnectionDelay>10000</reconnectionDelay>
7
<includeCallerData>${includeCallerData}</includeCallerData>
8
</appender>
9
10
<root level="DEBUG">
11
<appender-ref ref="SOCKET" />
12
</root>
13
14
</configuration>
Copied!
注意配置文件中的 remoteHostportincludeCallerData 属性的值并没有直接给出,而是通过占位符来代替。这些值可以通过系统属性来指定:
1
java -Dhost=localhost -Dport=6000 -DincludeCallerData=false \
2
chapters.appenders.socket.SocketClient2 src/main/java/chapters/appenders/socket/client1.xml
Copied!
这个命令应该会得到跟之前 SocketClient1 这个例子类似的结果。
让我再重复强调一遍,日志事件的序列化没有侵入性。反序列化出来的日志事件像其它的日志事件一样携带同样的信息。可以像操作本地日志事件一样操作它,除了序列化日志事件默认不会包含调用者的信息。下面通过一个例子来说明,首先通过一下命令启动 SimpleSocketServer
1
java ch.qos.logback.classic.net.SimpleSocketServer 6000 \
2
src/main/java/chapters/appenders/socket/server2.xml
Copied!
配置文件 server2.xml 创建了一个可以输出调用者文件名以及行号的 ConsoleAppender。如果你像之前通过配置文件 client1.xml 来运行 SocketClient2,你将会在两个括号之间看到两个问号,而不是调用者的文件名以及行号:
1
2006-11-06 17:37:30,968 DEBUG [Thread-0] [?:?] chapters.appenders.socket.SocketClient2 - Hi
Copied!
可以通过设置 includeCallerData 的值为 true,来改变 SocketAppender 的输出信息。使用如下命令:
1
java -Dhost=localhost -Dport=6000 -DincludeCallerData=true \
2
chapters.appenders.socket.SocketClient2 src/main/java/chapters/appenders/socket/client1.xml
Copied!
因为反序列化出来的事件可以像本地日志时间一样被处理,所以它们甚至可以被传送到第二个服务器做进一步的处理。在练习的时候,你可以设置两台服务器,第一台服务器接受客户端的日志事件,然后转发到第二台服务器。
使用 SimpleSSLSocketServer
SimpleSSLSocketServer 跟之前使用的 SimpleSocketServer 一样,在命令行接收两个参数:portconfigFile。此外,你必须为日志服务器的 X.509 认证通过系统属性提供位置与密码信息。
logback-examples/ 文件夹下,通过如下命令来启动 SimpleSSLSocketServer
1
java -Djavax.net.ssl.keyStore=src/main/java/chapters/appenders/socket/ssl/keystore.jks \
2
-Djavax.net.ssl.keyStorePassword=changeit \
3
ch.qos.logback.classic.net.SimpleSSLSocketServer 6000 \
4
src/main/java/chapters/appenders/socket/ssl/server.xml
Copied!
SimpleSSLSocketServer 示例使用 X.509 认证,非常适合测试以及实验。在生产环境使用 SimpleSSLSocketServer 之前,你应该为你的日志服务器获取一个 X.509 认证来标识你的服务器。详情请参考第十五章
因为服务器的配置文件在根元素上指定了 debug="true",所以你将会在服务器启动的过程中看到 SSL 的配置信息。这在验证本地安全策略是否被正确实现时非常有效。
SimpleSSLSocketServer 启动的时候,你可以使用 SSLSocketAppender 来连接服务器。下面这个例子展示所需要的配置信息:
1
<configuration debug="true">
2
3
<appender name="SOCKET" class="ch.qos.logback.classic.net.SSLSocketAppender">
4
<remoteHost>${host}</remoteHost>
5
<port>${port}</port>
6
<reconnectionDelay>10000</reconnectionDelay>
7
<ssl>
8
<trustStore>
9
<location>${truststore}</location>
10
<password>${password}</password>
11
</trustStore>
12
</ssl>
13
</appender>
14
15
<root level="DEBUG">
16
<appender-ref ref="SOCKET" />
17
</root>
18
19
</configuration>
Copied!
注意,跟之前一样,remoteHostport 的值通过占位符来指定。另外,注意一下 ssl 属性,它包含了一个内置属性 trustStore,通过这个属性来指定信用商店的位置以及密码。这个配置非常的必要,因为我们使用自签名的证书。见第十五章来查看更多使用 SSLSocketAppender 关于 SSL 的配置信息。
我们使用这个配置来运行一个客户端实例,在命令行通过系统属性指定占位符的值:
1
java -Dhost=localhost -Dport=6000 \
2
-Dtruststore=file:src/main/java/chapters/appenders/socket/ssl/truststore.jks \
3
-Dpassword=changeit \
4
chapters.appenders.socket.SocketClient2 src/main/java/chapters/appenders/socket/ssl/client.xml
Copied!
跟之前的示例一样,在客户端提示的时候输入一些信息,然后这些信息将会传送到服务端(使用安全的通道),并展示在控制台。
注意, truststore 的值是通过在命令行使用系统属性指定了一个标识信用商店的文件路径。你也可以使用 classpath 路径,具体参考第十五章
我们可以看到跟服务器启动时类似的信息,因为我们在客户端的配置文件的根元素中添加了 debug="true",客户端启动的时候,详细打印了 SSL 的配置信息来帮助我们验证本地策略的一致性。

ServerSocketAppender and SSLServerSocketAppender

我们之前讨论过的 SocketAppender 组件(以及具有 SSL 能力的副本)被设计成允许应用程序通过网络连接远程日志服务器,以便传输日志事件。在某些情况下,通过应用程序初始化一个对日志服务器的连接可能不方便或者不可行。在这些情况下,logback 提供了 ServerSocketAppender
ServerSocketAppender 不会初始化一个到日志服务器的连接,而是被动的监听 TCP 端口,等待客户端的连接。日志事件被传输给这个 appender,然后再分发给每个连接的客户端。如果没有客户端连接,日志事件会被马上丢弃。
除了基本的 ServerSocketAppender 之外,logback 还提供了 SSLServerSocketAppender,它通过一个安全,加密的通道传输日志事件到每个连接的客户端。而且,具有 SSL 功能的 appender 完全支持基于证书的双向认证,只有认证通过的客户端才可以连接到这个 appender 去接收日志事件。
对日志事件进行编码再传输的方法与 SocketAppender 完全一致,每个日志事件都是 ILoggingEvent 的实例。只不过连接的起始方向是相反的。虽然 SocketAppender 充当了一个主动的角色去连接日志服务器,ServerSocketAppender 是被动的监听即将到来的日志事件。
ServerSocketAppender 的子类型只提供给 logback 的接收组件。关于接收组件的信息,请查看第十四章
ServerSocketAppender 支持如下的配置属性:
属性名
类型
描述
address
String
appender 监听的本地网络接口地址。如果没有指定,则监听所有的网络接口
includeCallerData
boolean
如果为 true,调用者的信息将会发送给远程服务器。为 false,则不发送。
port
int
appender 监听的端口
ssl
SSLConfiguration
仅仅支持 SSLServerSocketAppender。具体参见第十五章
以下是关于 ServerSocketAppender 的配置:
1
<configuration debug="true">
2
<appender name="SERVER" class="ch.qos.logback.classic.net.server.ServerSocketAppender">
3
<port>${port}</port>
4
<includeCallerData>${includeCallerData}</includeCallerData>
5
</appender>
6
7
<root level="debug">
8
<appender-ref ref="SERVER" />
9
</root>
10
</configuration>
Copied!
注意这个配置跟之前 SocketAppender 的配置只有 class 这个属性不同。remoteHost 缺失表示 appender 被动的等待远程主机的连接,而不是新开一个到远程日志服务器的连接。
以下是关于 SSLServerSocketAppender 的配置:
1
<configuration debug="true">
2
<appender name="SERVER"
3
class="ch.qos.logback.classic.net.server.SSLServerSocketAppender">
4
<port>${port}</port>
5
<includeCallerData>${includeCallerData}</includeCallerData>
6
<ssl>
7
<keyStore>
8
<location>${keystore}</location>
9
<password>${password}</password>
10
</keyStore>
11
</ssl>
12
</appender>
13
14
<root level="debug">
15
<appender-ref ref="SERVER" />
16
</root>
17
18
</configuration>
Copied!
这个配置跟上一个配置主要的不同在于 appender 的 class 属性为 SSLServerSocketAppender 类型,包含一个嵌套的 ssl 元素。在这个例子中,为 appender 配置了 X.509 认证的 keyStore。
具体的 SSL 配置参见第十五章,这句话翻的我想吐了
因为 ServerSocketAppender 的子类是专门为接收组件设计的,所以我们将对这个的阐述推迟到 第十四章 介绍。

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 将在接下来的章节讨论。 邮件应该有一个主题行,对应触发的邮件信息。 假设 subject 的值为:"Log: %Logger - %msg",触发事件的 logger 名为 "com.foo.Bar",并且日志信息为 "Hello world"。那么发出的邮件信息将会有一个名为 "Log: com.foo.Bar - Hello World" 的主题行。 默认情况下,这个属性的值为 "%logger{20} - %m"
discriminator
在 Discriminator 的帮助下,SMTPAppender 根据 discriminator 返回的值可以将不同日志事件分散到不同的缓冲区中。默认的 discriminator 将返回同一个值,所以所有的事件都使用同一个缓冲区。
evaluator
通过创建一个新的 元素来声明此选项。通过 class 属性指定 class 的名字表示用户希望通过哪个类来满足 SMTPAppenderEvaluator 的需要。 如果没有指定此选项,当触发一个大于等于 ERROR 级别的事件时,SMTPAppender 将会被分配一个 OnErrorEvaluator 的实例。 logback 配备了几个其它的 evaluator,分别叫 OnMarkerEvaluator (将在下面讨论),一个相对强大的 evaluator 叫 JaninoEventEvaluator(在其它章节讨论) 以及最近版本才有的一个更加强大的 evaluator 叫 GEventEvaluator
cyclicBufferTracker
从名字可以看出,是一个 CyclicBufferTracker 的实例追踪循环缓冲区。它基于 discriminator 返回的 key (见上)。 如果你不想指定一个 cyclicBufferTracker,那么将会自动创建一个 CyclicBufferTracker 的实例。默认的,这个实例用来保留事件的循环缓冲区的大小为 256。你需要改变 bufferSize 选项的大小(见下面)
username
String
默认为 null
password
String
默认为 null
STARTTLS
boolean
如果为 true,那么 appender 将会发送 STARTTLS 命令(如果服务器支持)将连接变成 SSL 连接。注意,连接初始的时候是为加密的。默认为 false。
SSL
boolean
如果为 true,将通过 SSL 连接服务器。默认为 false。
charsetEncoding
String
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 属性。 通过 JNDI 获取 Session 实例可以减少需要配置的数量,使你的应用减少重复([dryer](http://en.wikipedia.org/wiki/Don't_repeat_yourself))的工作。更多关于在 Tomcat 配置 JNDI 的信息请参考 JNDI Resources How-to注意:通过 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
1
<configuration>
2
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
3
<smtpHost>SMTP 服务器的地址</smtpHost>
4
<to>收件人1</to>
5
<to>收件人2</to>
6
<from>发件人</from>
7
<subject>TESTING: %logger{20} - %m</subject>
8
<layout class="ch.qos.logback.classic.PatternLayout">
9
<pattern>%date %-5level %logger{35} - %message%n</pattern>
10
</layout>
11
</appender>
12
13
<root level="DEBUG">
14
<appender-ref ref="EMAIL" />
15
</root>
16
</configuration>
Copied!
在使用以上配置测试 chapters.appenders.mail.Email 之前,你需要设置 smtpHosttofrom 的值。一旦你正确设置了配置文件的值,你可以通过一下命令来执行:
1
java chapters.appenders.mail.EMail 100 src/main/java/chapters/appenders/mail/mail1.xml
Copied!
收件者收到的邮件是经过 PatternLayout 格式化后的 100 条日志。下图展示的就是 Mozilla Thunderbird 邮件客户端接收到的邮件。
smtpAppender1
下个例子配置文件 mail2.xml 中的 smtpHosttofrom 属性的值通过占位符来代替。下面是 mail2.xml 配置中的一部分:
1
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
2
<smtpHost>${smtpHost}</smtpHost>
3
<to>${to}</to>
4
<from>${from}</from>
5
<layout class="ch.qos.logback.classic.html.HTMLLayout"/>
6
</appender>
Copied!
你可以通过命令行来传递参数:
1
java -Dfrom=[email protected] -Dto=[email protected] -DsmtpHost=some_smtp_host \
2
chapters.appenders.mail.EMail 10000 src/main/java/chapters/appenders/mail/mail2.xml
Copied!
根据你的环境替换合适的值。
注意在新的例子中,PatternLayoutHTMLLayout 替代,将日志格式化为 HTML 表格。你可以更改行与列的顺序,以及表格的 CSS 样式。查看 HTMLLayout 文档更详细的信息。
由于给定的循环缓冲区的大小为 256,收件人可以看到经过 256 条经过格式化的日志显示在 HTML 表格中。注意,chapters.appenders.mail.Email 应用生成了 10'000 条日志,但是只有最新的 256 条日志会显示在邮件中。
smtpAppender2
像 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
1
<configuration>
2
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
3
<smtpHost>${smtpHost}</smtpHost>
4
<to>${to}</to>
5
<from>${from}</from>
6
<subject>%logger{20} - %m</subject>
7
<layout class="ch.qos.logback.classic.html.HTMLLayout" />
8
9
<cyclicBufferTracker
10
class="ch.qos.logback.core.spi.CyclicBufferTracker">
11
<!-- 每封邮件只包含一条日志 -->
12
<bufferSize>1</bufferSize>
13
</cyclicBufferTracker>
14
</appender>
15
16
<root level="DEBUG">
17
<appender-ref ref="EMAIL" />
18
</root>
19
</configuration>
Copied!

触发事件

如果 evaluator 属性没有设置,当日志事件的级别为 ERROR 时,SMTPAppender 会创建一个 OnErrorEvaluator 实例来触发发送邮件。发送错误时发送邮件是比较合理的,EventEvaluator 提供了不同的实现来重写默认的方法。
SMTPAppender 提交的每一个日志事件通过 evaluator 都会调用 evaluate() 方法来决定这个事件是否需要发送邮件,或者仅仅是替换缓冲区中的内容。当 evaluator 给了一个确定的答案,那么将会发送邮件。SMTPAppender 有且只有一个 evaluator 实例。这个实例能够管理它自己的内部状态。为了对这个进行说明,CounterBasedEvaluator 实现了一个 evaluator,当第 1024 个日志事件到来时才会触发邮件发送。
1
package chapters.appenders.mail;
2
3
import ch.qos.logback.core.boolex.EvaluationException;
4
import ch.qos.logback.core.boolex.EventEvaluator;
5
import ch.qos.logback.core.spi.ContextAwareBase;
6
7
public class CounterBasedEvaluator extends ContextAwareBase implements EventEvaluator {
8
9
static int LIMIT = 1024;
10
int counter = 0;
11
String name;
12
13
public boolean evaluate(Object event) throws NullPointerException,
14
EvaluationException {
15
counter++;
16
17
if (counter == LIMIT) {
18
counter = 0;
19
20
return true;
21
} else {
22
return false;
23
}
24
}
25
26
public String getName() {
27
return name;
28
}
29
30
public void setName(String name) {
31
this.name = name;
32
}
33
}
Copied!
这个类继承了 ContextAwareBase 以及实现了 EventEvaluator 接口。这可以让用户专注在 EventEvaluator 的核心功能上,让基类提供基础的功能。
设置 SMTPAppenderevaluator 选项来指定用户自定义的 evaluator。下面的例子将 SMTPAppender 附加在 root logger 上,并使用 CounterBasedEvaluator 作为它事件的 evaluator。
Example: mail3.xml
1
<configuration>
2
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
3
<evaluator class="chapters.appenders.mail.CounterBasedEvaluator" />
4
<smtpHost>${smtpHost}</smtpHost>
5
<to>${to}</to>
6
<from>${from}</from>
7
<subject>%logger{20} - %m</subject>
8
9
<layout class="ch.qos.logback.classic.html.HTMLLayout"/>
10
</appender>
11
12
<root level="DEBUG">
13
<appender-ref ref="EMAIL" />
14
</root>
15
</configuration>
Copied!

基于标记(Marker)触发

虽然通过默认通过错误级别的日志来触发邮件的发送是合理的,但是可能会导致太多的邮件充斥用户的邮箱。logback 自带了另外一个触发策略,叫做 OnMarkerEvaluator。它基于标记来触发。其实就是通过用户指定的标记来触发。下面这个例子说明这个一点:
Marked_EMail 应用包含了几个日志语句,都是 ERROR 级别的。但是只有其中的一条被标记。下面的是相关的代码:
1
Marker notifyAdmin = MarkerFactory.getMarker("NOTIFY_ADMIN");
2
logger.error(notifyAdmin,
3
"This is a serious an error requiring the admin's attention",
4