├── images
├── log.png
├── multiple.gif
├── mx4j_jetty.gif
├── statusList.gif
├── basicSyntax.png
├── dbAppenderLE.gif
├── htmlLayout0.gif
├── htmlLayout1.png
├── mx4j_tomcat.gif
├── smtpAppender1.jpg
├── smtpAppender2.jpg
├── appenderSyntax.png
├── countingFilter.png
├── jmxConfigurator.gif
├── socketReceiver.png
├── statusMessages.png
├── htmlLayoutAccess.gif
├── jconsole15_jetty.gif
├── jconsole15_tomcat.gif
├── appenderClassDiagram.jpg
├── dbAppenderLEProperty.gif
├── serverSocketReceiver.png
├── basic-selection-rule..png
├── dbAppenderLEException.gif
└── underTheHoodSequence2.gif
├── .gitbook.yaml
├── .gitignore
├── SUMMARY.md
├── 01第一章:logback 介绍.md
├── README.md
├── 05第五章:Encoder.md
├── 13第十三章:从 log4j 迁移.md
├── 10第十章:JMX 配置器.md
├── 09第九章:日志隔离.md
├── 12第十二章:Groovy 配置.md
├── 14第十四章:Receivers.md
├── 02第二章:架构.md
├── 11第十一章:Joran.md
├── 08第八章:MDC.md
├── 15第十五章:使用 SSL.md
├── 07第七章:Filters.md
└── LICENSE
/images/log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/log.png
--------------------------------------------------------------------------------
/.gitbook.yaml:
--------------------------------------------------------------------------------
1 | root: ./
2 |
3 | structure:
4 | readme: README.md
5 | summary: SUMMARY.md
--------------------------------------------------------------------------------
/images/multiple.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/multiple.gif
--------------------------------------------------------------------------------
/images/mx4j_jetty.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/mx4j_jetty.gif
--------------------------------------------------------------------------------
/images/statusList.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/statusList.gif
--------------------------------------------------------------------------------
/images/basicSyntax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/basicSyntax.png
--------------------------------------------------------------------------------
/images/dbAppenderLE.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/dbAppenderLE.gif
--------------------------------------------------------------------------------
/images/htmlLayout0.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/htmlLayout0.gif
--------------------------------------------------------------------------------
/images/htmlLayout1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/htmlLayout1.png
--------------------------------------------------------------------------------
/images/mx4j_tomcat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/mx4j_tomcat.gif
--------------------------------------------------------------------------------
/images/smtpAppender1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/smtpAppender1.jpg
--------------------------------------------------------------------------------
/images/smtpAppender2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/smtpAppender2.jpg
--------------------------------------------------------------------------------
/images/appenderSyntax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/appenderSyntax.png
--------------------------------------------------------------------------------
/images/countingFilter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/countingFilter.png
--------------------------------------------------------------------------------
/images/jmxConfigurator.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/jmxConfigurator.gif
--------------------------------------------------------------------------------
/images/socketReceiver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/socketReceiver.png
--------------------------------------------------------------------------------
/images/statusMessages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/statusMessages.png
--------------------------------------------------------------------------------
/images/htmlLayoutAccess.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/htmlLayoutAccess.gif
--------------------------------------------------------------------------------
/images/jconsole15_jetty.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/jconsole15_jetty.gif
--------------------------------------------------------------------------------
/images/jconsole15_tomcat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/jconsole15_tomcat.gif
--------------------------------------------------------------------------------
/images/appenderClassDiagram.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/appenderClassDiagram.jpg
--------------------------------------------------------------------------------
/images/dbAppenderLEProperty.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/dbAppenderLEProperty.gif
--------------------------------------------------------------------------------
/images/serverSocketReceiver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/serverSocketReceiver.png
--------------------------------------------------------------------------------
/images/basic-selection-rule..png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/basic-selection-rule..png
--------------------------------------------------------------------------------
/images/dbAppenderLEException.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/dbAppenderLEException.gif
--------------------------------------------------------------------------------
/images/underTheHoodSequence2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YLongo/logback-chinese-manual/HEAD/images/underTheHoodSequence2.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node rules:
2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
3 | .grunt
4 |
5 | ## Dependency directory
6 | ## Commenting this out is preferred by some people, see
7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
8 | node_modules
9 |
10 | # Book build output
11 | _book
12 |
13 | # eBook build output
14 | *.epub
15 | *.mobi
16 | *.pdf
17 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [第一章:logback 介绍](01第一章:logback%20介绍.md)
4 | * [第二章:架构](02第二章:架构.md)
5 | * [第三章:logback 的配置](03第三章:logback%20的配置.md)
6 | * [第四章:Appenders](04第四章:Appenders.md)
7 | * [第五章:Encoder](05第五章:Encoder.md)
8 | * [第六章:Layouts](06第六章:Layouts.md)
9 | * [第七章:Filters](07第七章:Filters.md)
10 | * [第八章:MDC](08第八章:MDC.md)
11 | * [第九章:日志隔离](09第九章:日志隔离.md)
12 | * [第十章:JMX 配置器](10第十章:JMX%20配置器.md)
13 | * [第十一章:Joran](11第十一章:Joran.md)
14 | * [第十二章:Groovy 配置](12第十二章:Groovy%20配置.md)
15 | * [第十三章:从 log4j 迁移](13第十三章:从%20log4j%20迁移.md)
16 | * [第十四章:Receivers](14第十四章:Receivers.md)
17 | * [第十五章:使用 SSL](15第十五章:使用%20SSL.md)
18 |
--------------------------------------------------------------------------------
/01第一章:logback 介绍.md:
--------------------------------------------------------------------------------
1 |
2 | ## 什么是 logback
3 |
4 | logback 继承自 log4j,它建立在有十年工业经验的日志系统之上。它比其它所有的日志系统更快并且更小,包含了许多独特并且有用的特性。
5 |
6 | ## 天才第一步
7 |
8 | ### 要求
9 |
10 | logback-classic 模块需要在 classpath 添加 slf4j-api.jar、logback-core.jar 以及 logback-classic.jar。
11 |
12 | *Example 1.1: Basic template for logging*
13 |
14 | ```java
15 | package chapters.introduction;
16 |
17 | import org.slf4j.Logger;
18 | import org.slf4j.LoggerFactory;
19 |
20 | public class HelloWorld1 {
21 |
22 | public static void main(String[] args) {
23 | Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
24 | logger.debug("hello world");
25 | }
26 | }
27 | ```
28 |
29 | 类 `HelloWorld1` 定义在包 `chapters.introduction` 中,它导入了两个类 `Logger`、`LoggerFactory`,这两个类定义在 SLF4J API 中,在 org.slf4j 包中。
30 |
31 | 在这个例子中,`main()` 包含了一个 DEBUG 级别的日志语句,输出信息为 "hello world"
32 |
33 | 运行 `HelloWord1` 将会在控制台看到一行日志。由于 logback 默认的配置策略:当没有默认的配置时,logback 将会在 root logger 中新增一个 `ConsoleAppender`
34 |
35 | ```
36 | 11:58:56.662 [main] DEBUG chapters.introduction.HelloWorld1 - hello world
37 | ```
38 |
39 | Logback 通过一个内部的状态系统来报告它本身的状态信息。发生在 logback 生命周期中的事件可以通过 `StatusManager` 来获取。
40 |
41 | *Example: Printing Logger Status*
42 |
43 | ```java
44 | package chapters.introduction;
45 |
46 | import org.slf4j.Logger;
47 | import org.slf4j.LoggerFactory;
48 |
49 | import ch.qos.logback.classic.LoggerContext;
50 | import ch.qos.logback.core.util.StatusPrinter;
51 |
52 | public class HelloWorld2 {
53 |
54 | public static void main(String[] args) {
55 | Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld2");
56 | logger.debug("Hello world");
57 |
58 | // 打印内部的状态
59 | LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
60 | StatusPrinter.print(lc);
61 | }
62 | }
63 | ```
64 |
65 | 运行结果如下:
66 |
67 | ```java
68 | 12:23:49.324 [main] DEBUG chapters.introduction.HelloWorld2 - Hello world
69 | 12:23:49,258 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
70 | 12:23:49,258 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
71 | 12:23:49,258 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
72 | 12:23:49,262 |-INFO in ch.qos.logback.classic.BasicConfigurator@2e5d6d97 - Setting up default configuration.
73 | ```
74 |
75 | Logback 没有找到 *logback-test.xml* 和 *logback.xml* 配置文件,所以通过默认的配置策略-添加一个基本的 `ConsoleAppender` 来进行配置。`Appender` 类被看作为输出的目的地。Appenders 包括 console,files,Syslog,TCP Sockets,JMS 等等其它的日志输出目的地。用户可以根据自己的情况轻松的创建自己的 Appender。
76 |
77 | 如果发生了错误,logback 会自动在控制台打印自己内部的状态信息。
78 |
79 | 前面给的例子相对简单。实际上在大型的应用中,日志记录不会有太大的区别。日志记录的一般形式不会有改变,只是配置方式会有不同。可能你想要根据自己的需求来配置 logback,接下来的章节中将会介绍如何进行配置。
80 |
81 | 在上面的例子中,我们通过 `StatusPrinter.print()` 打印了 logback 自身的内部状态。logback 的内部状态对查找 logback 相关的问题非常的有用。
82 |
83 | 通过如下的三个步骤可以启用 logback 来记录日志:
84 |
85 | 1. 配置 logback 环境。你可以通过简单或者复杂的方式来做,这个在后面会叙述到。
86 | 2. 如果你想在每个类中打印日志,那么你需要将当前类的全称或者当前类当作参数,调用 `org.slf4j.LoggerFactory.getLogger()` 方法。
87 | 3. 使用实例 logger 来调用不同的方法来打印日志。例:debug(),info(),warn(),error()。通过这些方法将会在配置好的 appender 中输出日志。
88 |
89 | ## 构建 logback
90 |
91 | logback 使用 Maven 作为构建工具。
92 |
93 | 如果你安装了 Maven,你可以在 logback 的解压文件夹中运行 `mvn install` 来构建 logback 以及它所包含的模块。Maven 会自动下载 logback 需要的其它类库。
94 |
95 | Logback 的压缩包包含了完整的源码,所以你可以根据自己的需要随意更改。而且只要你使用 LGPL 跟 EPL 协议,你甚至可以发布你更改后的版本。
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # logback-chinese-manual
2 | logback 中文手册
3 | > 翻译自 [The logback manual](https://logback.qos.ch/manual/index.html)
4 |
5 | [在线地址](https://logbackcn.gitbook.io/logback/)
6 |
7 | ## 简介
8 |
9 | Logback 继承自 log4j。
10 |
11 | Logback 的架构非常的通用,适用不同的使用场景。Logback 被分成三个不同的模块:logback-core,logback-classic,logback-access。
12 |
13 | logback-core 是其它两个模块的基础。logback-classic 模块可以看作是 log4j 的一个优化版本,它天然的支持 SLF4J,所以你可以随意的从其它日志框架(例如:log4j 或者 java.util.logging)切回到 logack。
14 |
15 | logback-access 可以与 Servlet 容器进行整合,例如:Tomcat、Jetty。它提供了 http 访问日志的功能。
16 |
17 | ## The logback manual
18 |
19 | 手册包括了最新版本的 logback,总共有 150 多页,以及许多具体的例子,主要包含以下基本的和高级的特性:
20 |
21 | - logback 的整体架构
22 | - 讨论 logback 最好的实践以及反模式
23 | - logback 的 xml 配置方式
24 | - appender
25 | - encoder
26 | - layout
27 | - filter
28 | - 上下文诊断
29 | - Joran - logback 的配置系统
30 |
31 | logback 手册尽可能详细的描述了 logback API,包括它的特性以及设计原理。logback 手册适用于那些使用 Java 但是是 logback 新手的人,也适合那些对 logback 有一定经验的人。在手册的帮助下,新手可以快速上手。
32 |
33 | * [第一章:logback 介绍](https://github.com/Volong/logback-chinese-manual/blob/master/01%E7%AC%AC%E4%B8%80%E7%AB%A0%EF%BC%9Alogback%20%E4%BB%8B%E7%BB%8D.md)
34 | * [第二章:架构](https://github.com/Volong/logback-chinese-manual/blob/master/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%EF%BC%9A%E6%9E%B6%E6%9E%84.md)
35 | * [第三章:logback 的配置](https://github.com/Volong/logback-chinese-manual/blob/master/03%E7%AC%AC%E4%B8%89%E7%AB%A0%EF%BC%9Alogback%20%E7%9A%84%E9%85%8D%E7%BD%AE.md)
36 | * [第四章:Appenders](https://github.com/Volong/logback-chinese-manual/blob/master/04%E7%AC%AC%E5%9B%9B%E7%AB%A0%EF%BC%9AAppenders.md)
37 | * [第五章:Encoder](https://github.com/Volong/logback-chinese-manual/blob/master/05%E7%AC%AC%E4%BA%94%E7%AB%A0%EF%BC%9AEncoder.md)
38 | * [第六章:Layouts](https://github.com/Volong/logback-chinese-manual/blob/master/06%E7%AC%AC%E5%85%AD%E7%AB%A0%EF%BC%9ALayouts.md)
39 | * [第七章:Filters](https://github.com/Volong/logback-chinese-manual/blob/master/07%E7%AC%AC%E4%B8%83%E7%AB%A0%EF%BC%9AFilters.md)
40 | * [第八章:MDC](https://github.com/Volong/logback-chinese-manual/blob/master/08%E7%AC%AC%E5%85%AB%E7%AB%A0%EF%BC%9AMDC.md)
41 | * [第九章:日志隔离](https://github.com/Volong/logback-chinese-manual/blob/master/09%E7%AC%AC%E4%B9%9D%E7%AB%A0%EF%BC%9A%E6%97%A5%E5%BF%97%E9%9A%94%E7%A6%BB.md)
42 | * [第十章:JMX 配置器](https://github.com/Volong/logback-chinese-manual/blob/master/10%E7%AC%AC%E5%8D%81%E7%AB%A0%EF%BC%9AJMX%20%E9%85%8D%E7%BD%AE%E5%99%A8.md)
43 | * [第十一章:Joran](https://github.com/Volong/logback-chinese-manual/blob/master/11%E7%AC%AC%E5%8D%81%E4%B8%80%E7%AB%A0%EF%BC%9AJoran.md)
44 | * [第十二章:Groovy 配置](https://github.com/Volong/logback-chinese-manual/blob/master/12%E7%AC%AC%E5%8D%81%E4%BA%8C%E7%AB%A0%EF%BC%9AGroovy%20%E9%85%8D%E7%BD%AE.md)
45 | * [第十三章:从 log4j 迁移](https://github.com/Volong/logback-chinese-manual/blob/master/13%E7%AC%AC%E5%8D%81%E4%B8%89%E7%AB%A0%EF%BC%9A%E4%BB%8E%20log4j%20%E8%BF%81%E7%A7%BB.md)
46 | * [第十四章:Receivers](https://github.com/Volong/logback-chinese-manual/blob/master/14%E7%AC%AC%E5%8D%81%E5%9B%9B%E7%AB%A0%EF%BC%9AReceivers.md)
47 | * [第十五章:使用 SSL](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md)
48 |
49 |
50 |
51 | 
52 |
53 | > 图片来源:1
54 |
55 | 推荐阅读:
56 | 1. [一个著名的日志系统是怎么设计出来的?](https://mp.weixin.qq.com/s/XiCky-Z8-n4vqItJVHjDIg)
57 | 2. [架构师必备,带你弄清混乱的JAVA日志体系!](https://mp.weixin.qq.com/s/8VvBdRH_Yc-Dt4HFGbC5rg)
58 |
--------------------------------------------------------------------------------
/05第五章:Encoder.md:
--------------------------------------------------------------------------------
1 | ## 什么是 encoder
2 |
3 | encoder 将日志事件转换为字节数组,同时将字节数组写入到一个 `OutputStream` 中。encoder 在 logback 0.9.19 版本引进。在之前的版本中,大多数的 appender 依赖 layout 将日志事件转换为 string,然后再通过 `java.io.Writer` 写出。在之前的版本中,用户需要在 `FileAppender` 中内置一个 `PatternLayout`。在 0.9.19 之后的版本中,`FileAppender` 以及子类[需要一个 encoder 而不是 layout](https://logback.qos.ch/codes.html#layoutInsteadOfEncoder)。
4 |
5 | 为什么会有这个改变?
6 |
7 | layout 将会在下一章节讨论,它只能将日志事件转换为成 string。而且,考虑到 layout 在日志事件写出时不能控制日志事件,不能将日志事件批量聚合。与之相反的是,encoder 不但可以完全控制字节写出时的格式,而且还可以控制这些字节什么时候被写出。
8 |
9 | `PatternLayoutEncoder` 是目前真正唯一有用的 encoder。它仅仅包裹了一个 `PatternLayout` 就完成了大部分的工作。因此,除了不必要的复杂性,encoder 似乎不会有太多的用处。但是,我们希望一个全新的更加强大的 encoder 来改变这种印象。
10 |
11 | ## Encoder 接口
12 |
13 | encoder 负责将日志事件转换为字节数组,并将字节数组写入到合适的 `OutputStream` 中。所以,encoder 可以完全控制将什么样的字节以及什么时候将字节写入到由 appender 维护的 `OutputStream` 中。下面是 [Encoder 接口:](https://logback.qos.ch/xref/ch/qos/logback/core/encoder/Encoder.html)
14 |
15 | ```java
16 | package ch.qos.logback.core.encoder;
17 |
18 | public interface Encoder extends ContextAware, LifeCycle {
19 |
20 | /**
21 | * This method is called when the owning appender starts or whenever output
22 | * needs to be directed to a new OutputStream, for instance as a result of a
23 | * rollover.
24 | */
25 | void init(OutputStream os) throws IOException;
26 |
27 | /**
28 | * Encode and write an event to the appropriate {@link OutputStream}.
29 | * Implementations are free to defer writing out of the encoded event and
30 | * instead write in batches.
31 | */
32 | void doEncode(E event) throws IOException;
33 |
34 |
35 | /**
36 | * This method is called prior to the closing of the underling
37 | * {@link OutputStream}. Implementations MUST not close the underlying
38 | * {@link OutputStream} which is the responsibility of the owning appender.
39 | */
40 | void close() throws IOException;
41 | }
42 | ```
43 |
44 | 正如你所看见的,`Encoder` 接口仅仅包含几个方法,但是令人惊讶的是这些方法可以完成许多有用的事情。
45 |
46 | ## LayoutWrappingEncoder
47 |
48 | 直到 logback 的 0.9.19 版本,许多 appender 依赖 layout 实例去控制日志的格式化输出。因为基于 layout 接口存在了大量的代码,所以我们需要一种方式容 encoder 与 layout 进行交互。[LayoutWrappingEncoder](https://logback.qos.ch/xref/ch/qos/logback/core/encoder/LayoutWrappingEncoder.html) 就是 encoder 与 layout 之间的桥梁。它实现了 encoder 接口并且包裹了一个 layout,通过委托该 layout 将日志事件转换为字符串。
49 |
50 | 下面是 `LayoutWrappingEncoder` 的部分代码,说明了如何委托包裹的 layout 实例去完成工作。
51 |
52 | ```java
53 | package ch.qos.logback.core.encoder;
54 |
55 | public class LayoutWrappingEncoder extends EncoderBase {
56 |
57 | protected Layout layout;
58 | private Charset charset;
59 |
60 | // encode a given event as a byte[]
61 | public byte[] encode(E event) {
62 | String txt = layout.doLayout(event);
63 | return convertToBytes(txt);
64 | }
65 |
66 | private byte[] convertToBytes(String s) {
67 | if (charset == null) {
68 | return s.getBytes();
69 | } else {
70 | return s.getBytes(charset);
71 | }
72 | }
73 | }
74 | ```
75 |
76 | `doLayout()` 方法首先通过包裹的 layout 将日志事件转换为字符串。返回的字符串结果根据用户设定的字符编码 (charset) 再转换为字节数组。
77 |
78 | ## PatternLayoutEncoder
79 |
80 | 由于 `PatternLayout` 是最常用的 layout,logback 使用 `PatternLayoutEncoder` 来满足这种用法。它扩展了 `LayoutWrappingEncoder`,被限制用来包裹 `PatternLayout` 实例。
81 |
82 | 在 logback 0.9.19 版本,无论 `FileAppender` 还是其子类通过 `PatternLayout` 来进行配置,都必须使用 `PatternLayoutEncoder` 来代替。具体的解释参见:[layoutInsteadOfEncoder](https://logback.qos.ch/codes.html#layoutInsteadOfEncoder)。
83 |
84 | #### immediateFlush 属性
85 |
86 | 在 `LOGBACK 1.2.0` 中, `immediateFlush` 属性是 appender 的一部分。
87 |
88 | #### 用格式化字符串作为开头
89 |
90 | 为了帮助解析日志文件,logback 可以将格式化字符串插入到日志文件的顶部。这个功能默认是**关闭**的。可以为相关的 `PatternLayoutEncoder` 设置 `outputPatternAsHeader` 属性的值为 `true` 来开启这个功能。下面是示例:
91 |
92 | ```java
93 |
94 | foo.log
95 |
96 | %d %-5level [%thread] %logger{0}: %msg%n
97 | true
98 |
99 |
100 | ```
101 |
102 | 将会在日志文件中输出类似下面的日志:
103 |
104 | ```java
105 | #logback.classic pattern: %d [%thread] %-5level %logger{36} - %msg%n
106 | 2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hello world
107 | 2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hi again
108 | ...
109 | ```
110 |
111 | 以 "#logback.classic pattern" 开头的行就是新插入的行。
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/13第十三章:从 log4j 迁移.md:
--------------------------------------------------------------------------------
1 | 本章涉及到的内容为将 log4j 的组件,例如 appender 或者 layout 迁移到 logback-classic。
2 |
3 | 仅仅调用 log4j 客户端 API 的软件,也就是 `org.apache.log4j` 包中 `Logger` 或者 `Category` 类,可以通过 [SLF4J 迁移工具](https://www.slf4j.org/migrator.html)使用 SLF4J 来进行自动迁移。为了将 *log4j.property* 文件转换为同等的 logback 配置,你可以使用 [log4j.properties 转换器](https://logback.qos.ch/translator/)。
4 |
5 | 在某种程度上来说,log4j 与 logback-classic 密切相关。核心组件,logger,appender 以及 layout 在两个框架中都存在,并且目的一致。类似的,最重要的内部数据结构,叫做 `LoggingEvent`,在两个框架中非常相似,但是实现完全不同。最主要的是,在 logback-classic 中,`LoggingEvent` 实现了 `ILoggingEvent` 接口。迁移 log4j 组件到 logback-classic 最大的改变在于 `LoggingEvent` 类相关的实现不同。但是,请放心,这些变化是有限的。如果你尽了最大的努力仍然不能将 log4j 组件迁移到 logback-classic,你可以通过 [logback 开发邮件列表](https://logback.qos.ch/mailinglist.html)来进行提问。logback 的开发者应该可以提供指导。
6 |
7 | ### 迁移 log4j 的 layout
8 |
9 | 假设我们现在要迁移一个简单的,名叫 [TrivialLog4jLayout](https://logback.qos.ch/xref/chapters/migrationFromLog4j/TrivialLog4jLayout.html) 的 log4j layout,它将日志事件中的消息作为格式化消息返回。代码如下:
10 |
11 | ```java
12 | package chapters.migrationFromLog4j;
13 |
14 | import org.apache.log4j.Layout;
15 | import org.apache.log4j.spi.LoggingEvent;
16 |
17 | public class TrivialLog4jLayout extends Layout {
18 |
19 | public void activateOptions() {
20 |
21 | }
22 |
23 | public String format(LoggingEvent loggingEvent) {
24 | return loggingEvent.getRenderedMessage();
25 | }
26 |
27 | public boolean ignoresThrowable() {
28 | return true;
29 | }
30 | }
31 | ```
32 |
33 | 等价的 logback-classic [TrivialLogbackLayout](https://logback.qos.ch/xref/chapters/migrationFromLog4j/TrivialLogbackLayout.html) 如下:
34 |
35 | ```java
36 | package chapters.migrationFromLog4j;
37 |
38 | import ch.qos.logback.classic.spi.ILoggingEvent;
39 | import ch.qos.logback.core.LayoutBase;
40 |
41 | public class TrivialLogbackLayout extends LayoutBase {
42 |
43 | public String doLayout(ILoggingEvent loggingEvent) {
44 | return loggingEvent.getMessage();
45 | }
46 | }
47 | ```
48 |
49 | 正如你所见,在 logback-classic layout 中,格式化的方法叫做 `doLayout`,而在 log4j 中叫 `format()`。因为在 logback-classic 中没有等价的方法,所以 `ignoresThrowable()` 方法则不需要。logback-classic layout 必须继承 `LayoutBase` 类。
50 |
51 | `activateOptions()` 方法的优点值得进一步讨论。在 log4j 中,一个 layout 有它自己的 `activateOptions()` 方法,通过 log4j 的配置程序,也就是 `PropertyConfigurator` 与 `DOMConfigurator`,会在 layout 所有的选项都设置完之后调用。因此,layout 有机会去检查它的所有的选项是否一致,如果是,那么开始进行初始化。
52 |
53 | 在 logback-classic 中,layout 必须实现 [LifeCycle](https://logback.qos.ch/xref/ch/qos/logback/core/spi/LifeCycle.html) 接口,该接口包含了一个 `start()` 方法。这个 `start()` 方法相当 log4j 中的 `activateOptions()` 方法。
54 |
55 | ### 迁移 log4j 的 appender
56 |
57 | 迁移 appender 与迁移 layout 相当的类似。下面是有一个名为 [TrivialLog4jAppender](https://logback.qos.ch/xref/chapters/migrationFromLog4j/TrivialLog4jAppender.html) 的简单 appender,它会在控制台输出由它的 layout 返回的字符串。
58 |
59 | ```java
60 | package chapters.migrationFromLog4j;
61 |
62 | import org.apache.log4j.AppenderSkeleton;
63 | import org.apache.log4j.spi.LoggingEvent;
64 |
65 |
66 | public class TrivialLog4jAppender extends AppenderSkeleton {
67 |
68 | protected void append(LoggingEvent loggingevent) {
69 | String s = this.layout.format(loggingevent);
70 | System.out.println(s);
71 | }
72 |
73 | public void close() {
74 | // nothing to do
75 | }
76 |
77 | public boolean requiresLayout() {
78 | return true;
79 | }
80 | }
81 | ```
82 |
83 | 在 logback-classic 中等价的写法为 [TrivialLogbackAppender](https://logback.qos.ch/xref/chapters/migrationFromLog4j/TrivialLogbackAppender.html),如下:
84 |
85 | ```java
86 | package chapters.migrationFromLog4j;
87 |
88 | import ch.qos.logback.classic.spi.ILoggingEvent;
89 | import ch.qos.logback.core.AppenderBase;
90 |
91 | public class TrivialLogbackAppender extends AppenderBase {
92 |
93 | @Override
94 | public void start() {
95 | if (this.layout == null) {
96 | addError("No layout set for the appender named [" + name + "].");
97 | return;
98 | }
99 | super.start();
100 | }
101 |
102 | @Override
103 | protected void append(ILoggingEvent loggingevent) {
104 | // AppenderBase.doAppend 只会在这个 appender 成功启动之后调用这个方法
105 | String s = this.layout.doLayout(loggingevent);
106 | System.out.println(s);
107 | }
108 | }
109 | ```
110 |
111 | 比较这两个类,你会发现 `append()` 方法的内容没有改变。`requiresLayout` 方法在 logback 中没有用到,所以它可以被移除。在 logback 中,`stop()` 方法与 log4j 中的 `close()` 方法等价。然而,logback-classic 中的 `AppenderBase` 包含一个没有实现的 `stop` 方法,但是在这个简单的 appender 已经足够了。
--------------------------------------------------------------------------------
/10第十章:JMX 配置器.md:
--------------------------------------------------------------------------------
1 | 顾名思义,`JMXConfigurator` 允许通过 JMX 来配置 logback。简单来说就是,它允许你从默认配置文件,指定的文件或者 URL 重新配置 logback,列出 logger 以及修改 logger 级别。
2 |
3 | ### 使用 JMX 配置器
4 |
5 | 如果你的运行在 JDK 1.6 或者更高的版本,那么你仅仅需要在命令行调用 `jconsole`,然后连接到你服务器上的 MBeanServer。如果你运行在老版本的 JVM 上,那么你需要查看[在服务上使用 JMX](https://logback.qos.ch/manual/jmxConfig.html#jmxEnablingServer)。
6 |
7 | 在配置文件中开启 `JMXConfigurator` 只需要一行。如下:
8 |
9 | ```xml
10 |
11 |
12 |
13 |
14 |
15 | %date [%thread] %-5level %logger{25} - %msg%n
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ```
24 |
25 | 在你通过 *jconsole* 连接到服务器上之后,在 MBeans 面板上,在 "ch.qos.logback.classic.jmx.Configurator" 文件夹下你可以看到几个选项。如下图所示:
26 |
27 | ### 在 `jconsole` 中查看 `JMXConfigurator` 的截图
28 |
29 | 
30 |
31 | 所以,你可以
32 |
33 | - 使用默认配置文件重新加载 logback 的配置
34 | - 通过指定的 URL 重新加载配置
35 | - 通过指定的文件重新加载配置
36 | - 设置指定的 logger 的级别。想要设置为 null,传递 "null" 字符串就可以
37 | - 获取指定 logger 的级别。返回值可以为 null
38 | - 或者指定 logger 的[有效级别](https://github.com/Volong/logback-chinese-manual/blob/master/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%EF%BC%9A%E6%9E%B6%E6%9E%84.md#%E6%9C%89%E6%95%88%E7%AD%89%E7%BA%A7%E5%8F%88%E7%A7%B0%E4%B8%BA%E7%AD%89%E7%BA%A7%E7%BB%A7%E6%89%BF)
39 |
40 | `JMXConfigurator` 将已经存在的 logger 以及状态当作属性进行展示。
41 |
42 | 这个状态列表可以帮助你诊断 logger 的内部状态。
43 |
44 | 
45 |
46 | ### 避免内存泄漏
47 |
48 | 如果你的应用部署在 web 服务器或者应用服务器上,注册的 `JMXConfigurator` 实例会从系统类加载器创建一个引用到你的应用中。在应用停止或者重新部署事,它会阻止垃圾回收,那么将会导致内存泄漏。
49 |
50 | 因此,除非你的应用是单机的 Java 应用,否则的话,你必须从 JVM 的 Mbeans 服务上注销 `JMXConfigurator` 实例。通过 `LoggerContext` 调用 `reset()` 方法将会自动注销任何 JMXConfigurator 实例。一个好的方法去重置 logger 上下文是通过 `javax.servlet.ServletContextListener` 中的 `contextDestroyed()` 方法。示例代码如下:
51 |
52 | ```java
53 | import javax.servlet.ServletContextEvent;
54 | import javax.servlet.ServletContextListener;
55 |
56 | import org.slf4j.LoggerFactory;
57 | import ch.qos.logback.classic.LoggerContext;
58 |
59 | public class MyContextListener implements ServletContextListener {
60 |
61 | public void contextDestroyed(ServletContextEvent sce) {
62 | LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
63 | lc.stop();
64 | }
65 |
66 | public void contextInitialized(ServletContextEvent sce) {
67 | }
68 | }
69 | ```
70 |
71 | ## `JMXConfigurator` 与多个 web 应用
72 |
73 | 如果你在同一个服务器上部署了多个 web 应用,并且你没有重写默认的[上下文选择器](https://github.com/Volong/logback-chinese-manual/blob/master/09%E7%AC%AC%E4%B9%9D%E7%AB%A0%EF%BC%9A%E6%97%A5%E5%BF%97%E9%9A%94%E7%A6%BB.md#%E4%B8%8A%E4%B8%8B%E6%96%87%E9%80%89%E6%8B%A9%E5%99%A8),以及你把 *logback-\*.jar* 与 *slf4j-api.jar* 放到了每个 web 应用的 *WEB-INF/lib* 文件夹下。之后默认每个 `JMXConfigurator` 实例将会注册在同一个名字下。也就是 "ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator" 。换句话说,在你每个 web 应用中,默认各种 `JMXConfigurator` 实例关联的 logger 上下文将会冲突。
74 |
75 | 为了避免这种不必要的冲突,你仅仅需要[设置应用的日志上下文](https://github.com/Volong/logback-chinese-manual/blob/master/03%E7%AC%AC%E4%B8%89%E7%AB%A0%EF%BC%9Alogback%20%E7%9A%84%E9%85%8D%E7%BD%AE.md#%E8%AE%BE%E7%BD%AE-context-%E7%9A%84%E5%90%8D%E5%AD%97),`JMXConfigurator` 将会自动使用你设置好的名字。
76 |
77 | 例如,如果部署两个名为 "Koala" 与 "Wombat" 的 web 应用,那么你可以在 Koala 的配置文件中这样写:
78 |
79 | ```xml
80 |
81 | Koala
82 |
83 | ...
84 |
85 | ```
86 |
87 | 在 Wombat 的配置文件中,你可以这样写:
88 |
89 | ```xml
90 |
91 | Wombatx
92 |
93 | ...
94 |
95 | ```
96 |
97 | 在 jconsole 的 MBeans 面板中,你可以看到两个不同的 `JMXConfigurator` 实例:
98 |
99 | 
100 |
101 | 通过 元素的 "objectName" 属性,你可以完全控制注册到 MBeans 服务中 JMXConfigurator 的名字。
102 |
103 | ### 在服务器中开启 JMX
104 |
105 | 如果你的服务器运行在 JDK 1.6 或者更高版本,那么 JMX 默认开启。
106 |
107 | 对于旧版的 JVM,我们建议你参考你所使用的 web 服务器上 JMX 相关的文档。这些文档在 [Tomcat](http://tomcat.apache.org/tomcat-6.0-doc/monitoring.html) 以及 [Jetty](http://docs.codehaus.org/display/JETTY/JMX) 中都可以获得。在这个文档中,我们将会详细叙述 Tomcat 与 Jetty 相关的配置步骤。
108 |
109 | #### 在 Jetty 中开启 JMX (在 JDK 1.5 以及 JDK 1.6 测试过)
110 |
111 | 接下来的已经在 JDK 1.5 及 1.6 中测试过。在 JDK 1.6 以及以后的版本中,你的服务器是默认开启 JMX 的,你可以但是不需要遵循下面所讨论的。在 JDK 1.5 下,添加 JMX 支持,只需要在 *$JETTY_HOME/etc/jetty.xml* 配置文件中添加一个额外的支持。下面是需要被添加的元素:
112 |
113 | ```xml
114 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | ```
128 |
129 | 如果你想通过 `jconsole` 访问 Jetty 中的 MBeans,那么你需要在启动 Jetty 前设置系统属性 "com.sun.management.jmxremote"。
130 |
131 | 对于单机版本的 Jetty,通过:
132 |
133 | ```java
134 | java -Dcom.sun.management.jmxremote -jar start.jar [config files]
135 | ```
136 |
137 | 如果你想将 Jetty 作为 Maven 插件启动,那么你需要通过 `MAVEN_OPTS` shell 变量设置系统属性 "com.sun.management.jmxremote":
138 |
139 | ```java
140 | MAVEN_OPTS="-Dcom.sun.management.jmxremote"
141 | mvn jetty:run
142 | ```
143 |
144 | 你可以通过 `jconsole` 访问 Jetty 的 MBeans 以及 logback 的 `JMXConfigurator`。
145 |
146 | 
147 |
148 | 在你连接上以后,你可以访问 `JMXConfigurator`,就像上面的[截图](https://github.com/Volong/logback-chinese-manual/blob/master/10%E7%AC%AC%E5%8D%81%E7%AB%A0%EF%BC%9AJMX%20%E9%85%8D%E7%BD%AE%E5%99%A8.md#%E5%9C%A8-jconsole-%E4%B8%AD%E6%9F%A5%E7%9C%8B-jmxconfigurator-%E7%9A%84%E6%88%AA%E5%9B%BE)一样。
149 |
150 | #### MX4j 与 Jetty (在 JDK 1.5 以及 1.6 测试过)
151 |
152 | 假设你已经下载了 [MX4J](http://mx4j.sourceforge.net/),你想要通过 MX4J 的 HTTP 接口访问 `JMXConfigurator`,你需要添加之前讨论过的配置,并且设置 managementPort。
153 |
154 | ```xml
155 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | 8082
165 |
166 |
167 |
168 |
169 |
170 | ```
171 |
172 | 而且,*mx4j-tools.jar* 需要添加到 Jetty 的类路径下。
173 |
174 | 如果你想将 Jetty 作为 Maven 的插件运行,那么你需要添加 *mx4j-tools* 作为依赖。
175 |
176 | ```xml
177 |
178 | org.mortbay.jetty
179 | maven-jetty-plugin
180 |
181 | path/to/jetty.xml
182 | ...
183 |
184 |
185 |
186 | mx4j
187 | mx4j-tools
188 | 3.0.1
189 |
190 |
191 |
192 | ```
193 |
194 | 在通过以上配置启动了 Jetty 之后,可以通过如下的 URL 访问 `JMXConfigurator` (查找 "ch.qos.logback.classic"):
195 |
196 | ```http
197 | http://localhost:8082/
198 | ```
199 |
200 | 下面是通过 MX4J 接口访问的截图信息:
201 |
202 | 
203 |
204 | #### 在 Tomcat 配置 JMX (在 JDK 1.5 以及 1.6 测试过)
205 |
206 | 如果你使用 JDK 1.6 以及以后的版本,你的服务器是默认开启 JMX 的,你可以但是不需要遵循下面所讨论的。在 JDK 1.5 下,需要在 Tomcat 的 *$TOMCAT_HOME/bin/catalina.bat/sh* shell 脚本中添加如下的行:
207 |
208 | ```shell
209 | CATALINA_OPTS="-Dcom.sun.management.jmxremote"
210 | ```
211 |
212 | 一旦通过这些配置启动后,可以通过在命令行输入如下命令来获取 Tomcat 的 MBeans 以及 logback 的 `JMXConfigurator`:
213 |
214 | ```shell
215 | jconsole
216 | ```
217 |
218 | 
219 |
220 | 在你连接上以后,你可以访问 `JMXConfigurator`,就像上面的[截图](https://github.com/Volong/logback-chinese-manual/blob/master/10%E7%AC%AC%E5%8D%81%E7%AB%A0%EF%BC%9AJMX%20%E9%85%8D%E7%BD%AE%E5%99%A8.md#%E5%9C%A8-jconsole-%E4%B8%AD%E6%9F%A5%E7%9C%8B-jmxconfigurator-%E7%9A%84%E6%88%AA%E5%9B%BE)一样。
221 |
222 | #### MX4J 与 Tomcat (在 JDK 1.5 以及 1.6 测试过)
223 |
224 | 你可能想要通过 MX4J 提供的 web 接口访问 JMX 组件。在这种情况下,下面是必须的步骤:
225 |
226 | 假设你已经下载了 [MX4J](http://mx4j.sourceforge.net/),将 *mx4j-tools.jar* 文件放到了 *$TOMCAT_HOME/bin/* 文件夹下。那么,添加如下的行到 *$TOMCAT_HOME/bin/catalina.sh* 配置文件中:
227 |
228 | ```shell
229 |
230 | CATALINA_OPTS="-Dcom.sun.management.jmxremote"
231 |
232 |
233 | CLASSPATH="$CLASSPATH":"$CATALINA_HOME"/bin/mx4j-tools.jar
234 | ```
235 |
236 | 最后,在 *$TOMCAT_HOME/conf/server.xml* 文件中声明一个新的 `Connector`:
237 |
238 | ```xml
239 |
245 | ```
246 |
247 | 一旦 Tomcat 启动后,你可以通过访问如下的 URL 找到 JMXConfigurator (查找 "ch.qos.logback.classic"):
248 |
249 | 下面是通过 MX4J 接口访问得到的截图:
250 |
251 | 
252 |
253 |
--------------------------------------------------------------------------------
/09第九章:日志隔离.md:
--------------------------------------------------------------------------------
1 | ## 问题:日志隔离
2 |
3 | 这个章节处理一个相对困难的问题,为在同一个 web 或 EJB 容器运行的多个客户端提供一个隔离的日志环境。在接下来的章节中,"应用" 一词用来表示 web 应用以及 J2EE 应用。在隔离的日志环境中,每个应用将会看到一个不同的 logback 环境。所以一个应用的 logback 配置不会影响到另一个。从技术角度看,每个 web 应用都会保存一份 `LoggerContext` 的独立拷贝。在 logback 中,`LoggerContext` 产生的每个 logger 对象只要在内存中存活,都会被保留。这个问题的一个变体是隔离应用的日志与容器本身的日志。
4 |
5 | ## 最简单的方法
6 |
7 | 假设你的容器支持子级优先加载,那么日志隔离可以通过在每个应用中内置一份 slf4j 与 logback 的 jar 包完成。对于 web 应用,将 slf4j 与 logback 的 jar 包放在 *WEB-INF/lib* 文件夹下,这种方式可以有效的赋予每个 web 应用隔离的日志环境。*logback.xml* 文件放在 *WEB-INF/classes* 下,当 logback 被加载进内存时,该配置会被加载。
8 |
9 | 由于容器提供了类加载器隔离,每个 web 应用将会加载自己拷贝的 `LoggerContext` 以及自己拷贝的 *logback.xml*。
10 |
11 | 其实,也不全是。有时候你会被迫将 SLF4J 与 logback 的 jar 包放在同一个地方供所有的应用访问。通常,这是因为共享库需要使用 SLF4J。在这种情况下,所有的应用将会共享同一个日志环境。有许多其它的应用场景需要将 SLF4J 与 logback 的 jar 包放在同一个地方,那么所有的应用都会看到,这样就不能通过类加载隔离来对日志环境进行隔离了。但是并不是没有其它的办法,请继续往下阅读。
12 |
13 | ## 上下文选择器
14 |
15 | logback 提供了一种机制为每个单独 SLF4J 实例以及 logback 类加载进内存提供了多个 logger context。当你写下:
16 |
17 | ```java
18 | Logger logger = LoggerFactory.getLogger("foo");
19 | ```
20 |
21 | `LoggerFactory` 类中的 `getLogger()` 方法会要求 SLF4J 绑定 `ILoggerFactory`。当 SLF4J 绑定到 logback,会委托 [ContextSelector](https://logback.qos.ch/apidocs/ch/qos/logback/classic/selector/ContextSelector.html) 实例去返回 `ILoggerFactory`。`ContextSelector` 的实现一直都会返回同一个 `LoggerContext` 实例,也就是默认的 logger context。
22 |
23 | 你可以通过 *logback.ContextSelector* 这个系统属性指定不同的上下文选择器。假设你想指定 `myPackage.myContextSelector` 这个类的实例为上下文选择器,那么你可以通过如下方式添加一个系统属性:
24 |
25 | ```java
26 | -Dlogback.ContextSelector=myPackage.myContextSelector
27 | ```
28 |
29 | 这个上下文选择器需要实现 `ContextSelector` 这个接口,并且有一个构造方法,该构造方法仅仅只接收 `LoggerContext` 实例作为参数。
30 |
31 | ### ContextJNDISelector
32 |
33 | logback-classic 附带了一个名为 `ContextJNDISelector` 的选择器,它基于 JNDI 查找的有效数据去选择一个 logger 上下文。这个方法利用 J2EE 规范强制 JNDI 数据分离。因此,同样的环境变量在不同的应用中能够设置不同的值。换句话说,在不同的应用中调用 `LoggerFactory.getLogger()` 不同 logger 上下文中的 logger。即使所有的应用都共享内存中同一个 LoggerFactory 类。这样就能够对你的日志进行隔离。
34 |
35 | 要开启 `ContextJNDISelector`,需要设置系统属性 *logback.ContextSelector* 为 "JNDI"。如下:
36 |
37 | ```java
38 | -Dlogback.ContextSelector=JNDI
39 | ```
40 |
41 | 注意,`JNDI ` 是 `ch.qos.logback.classic.selector.ContextJNDISelector` 的缩写形式。
42 |
43 | ### 在应用中设置 JNDI
44 |
45 | 在你的每个应用中,你需要为应用命名 logger 上下文。对于 web 应用,通过 *web.xml* 指定 JNDI 环境条目。如果你应用的名字为 "kenobi",那么你可以添加如下的 XML 元素到你的 web.xml 文件中:
46 |
47 | ```xml
48 |
49 | logback/context-name
50 | java.lang.String
51 | kenobi
52 |
53 | ```
54 |
55 | 假设你已经开启了 `ContextJNDISelector`,那么将使用一个名为 "kenobi" 的 logger 上下文来为 Kenobi 打印日志。而且,logger 上下文 "kenobi" 会按照约定使用线程上下文类加载器去寻找一个名为 *logback-kenobi.xml* 的配置文件进行初始化。因此,对于例子中 "kenobi" web 应用,应该将 *logback-kenobi.xml* 放在 *WEB-INF/classes* 文件夹下。
56 |
57 | 只要你喜欢,通过设置 "logback/configuration-resource" JNDI 变量,你可以不按照约定,而是指定一个不同的配置文件。例如,对于 "kenobi" web 应用,如果你想指定配置文件为 *aFolder/my_config.xml* 而不是约定的 *logback-kenobi.xml*,你可以在 web.xml 中添加如下的 xml 元素:
58 |
59 | ```xml
60 |
61 | logback/configuration-resource
62 | java.lang.String
63 | aFolder/my_config.xml
64 |
65 | ```
66 |
67 | *my_config.xml* 文件需要放在 *WEB-INF/classes/aFolder/* 下。需要记住的一点是,使用当前线程的上下文类加载来查找配置文件作为 Java 资源。
68 |
69 | ### 通过 Tomcat 配置 ContextJNDISelector
70 |
71 | 首先,将 logback 的 jar 包 (logback-classic-1.3.0-alpha4.jar, logback-core-1.3.0-alpha4.jar and slf4j-api-1.8.0-beta1.jar) 放在 Tomcat 全局的类文件夹下。在 Tomcat 6.x 中,这个文件夹是 *$TOMCAT_HOME/lib/*。
72 |
73 | 系统属性 *logback.ContextSelector* 可以在 *catalina.sh* 脚本中添加如下的行来进行设置。Windows 下为 *catalina.bat*。在 *$TOMCAT_HOME/bin* 文件夹下。
74 |
75 | ```java
76 | JAVA_OPTS="$JAVA_OPTS -Dlogback.ContextSelector=JNDI"
77 | ```
78 |
79 | ### 应用热部署
80 |
81 | 当 web 应用被回收或者关闭时,我们强烈推荐关闭现有的 `LoggerContext`,以便正确的进行垃圾回收。logback 附带了一个名为 [`ContextDetachingSCL`](https://logback.qos.ch/xref/ch/qos/logback/classic/selector/servlet/ContextDetachingSCL.html) 的 `ServletContextListener`,用来分离旧 web 应用中的 `ContextSelector` 实例。可以在 *web.xml* 中添加如下的行来指定:
82 |
83 | ```xml
84 |
85 | ch.qos.logback.classic.selector.servlet.ContextDetachingSCL
86 |
87 | ```
88 |
89 | **`NOTE`** 大部分的容器会按照声明的顺序调用 `contextInitialized()` 方法,但是按照相反的顺序调用 `contextDestroyed()` 方法。也就是说,如果你在 *web.xml* 中声明了多个 `ServletContextListener`,那么 `ContextDetachingSCL` 应该*第一个*声明,那么在应用关闭的时候,它的 `contextDestroyed()` 方法将会在 *最后* 被调用。
90 |
91 | ### 更好的性能
92 |
93 | 当 `ContextJNDISelector` 处于活动状态,每次查找一个 logger,JNDI 查找必须被执行。这样对性能会有影响,特别是当你使用一个非静态的 logger 时。logback 附带了一个名为 [LoggerContextFilter](https://logback.qos.ch/xref/ch/qos/logback/classic/selector/servlet/LoggerContextFilter.html) 的过滤器,为了避免 JNDI 查找的消耗而特意设计。可以通过在 web.xml 中添加如下的行来指定:
94 |
95 | ```xml
96 |
97 | LoggerContextFilter
98 | ch.qos.logback.classic.selector.servlet.LoggerContextFilter
99 |
100 |
101 | LoggerContextFilter
102 | /*
103 |
104 | ```
105 |
106 | 在每个 http 请求开始的时候,`LoggerContextFilter` 会获取应用相关的 logger 上下文,然后把它放在 `ThreadLocal` 变量中。`ContextJNDISelector` 会看 `ThreadLocal` 变量是否已经被设置,如果是,那么 JNDI 查找将会跳过。在每个 http 请求结束的时候,`ThreadLocal` 变量就会为 null。使用 `LoggerContextFilter` 会大幅度的提高 logger 检索性能。
107 |
108 | 使 `ThreadLocal` 为 null,可以让 web 应用在停止或者回收时对它进行垃圾回收。
109 |
110 | ## 在共享库中改进静态引用
111 |
112 | 当 SLF4J 与 logback 组件被所有应用共享时,`ContextJNDISelector` 可以很好的创建日志隔离。当 `ContextJNDISelector` 处于活动状态时,每次对 `LoggerFactory.getLogger()` 的调用将会返回一个属于正在被调用/当前应用的一个 logger 上下文中的 logger。
113 |
114 | 通常是通过静态引用来引用一个 logger。(你们前面的例子全部是通过实例变量来引用😓)例如:
115 |
116 | ```java
117 | public class Foo {
118 | static Logger logger = LoggerFactory.getLogger(Foo.class);
119 | ...
120 | }
121 | ```
122 |
123 | 静态 logger 引用在内存以及在 CPU 中都是有效率的。使用一个 logger 引用用于该类的所有实例。而且,当加载类到内存中时,这个 logger 实例只会被查找一次。如果宿主类属于某个应用程序,比如 kenobi,那么这个静态的 logger 将通过抽象的 `ContextJNDISelector` 附加到 kenobi 的 logger 上下文中。类似的,如果宿主类属于其它的应用,比如 yoda,那么它的静态 logger 引用将会再一次通过抽象的 `ContextJNDISelector` 附加到 logger 的上下文中。
124 |
125 | 如果一个名为 `Mustafar` 的类,属于某个类库,被 *kenobi* 与 *yoda* 共享。只要 `Mustafar` 没有使用静态引用,那么每次对 `LoggerFactory.getLogger()` 的调用将会返回一个属于正在被调用/当前的应用的 logger 上下文中的 logger。但是如果 `Mustafar` 有一个静态的引用,那么当应用第一次调用它时,这个 logger 将会被附加到 logger 上下文中。因此,一旦共享类使用静态的 logger 引用, `ContextJNDISelector` 不会提供日志隔离。这个问题很长时间都没有得到解决。
126 |
127 | 透明且完美的解决这个问题的唯一方法是引入另一个间接级别的内部 logger,这样每个 logger 以某种方式将工作委托给一个附加在合适上下文的内部 logger 上。这个方法实现起来非常困难,并且会产生大量的计算开销。这个并不是我们追求的方式。(无语了😶,前面说完美,然后马上说不行)
128 |
129 | 不言而喻,一个非常简单方式去解决 "共享类的静态 logger" 问题的方式是将共享类移到 web 应用的内部。(也就是不进行共享) (废话一大堆,罗里吧嗦)。如果不共享不可能实现,我们可以使用 [`SiftingAppender`](https://logback.qos.ch/manual/appenders.html#SiftingAppender) 使用 JNDI 数据作为隔离标准去进行日志隔离。
130 |
131 | > 这段话翻译的我真的是蛋疼的很。外国人不是思维特别严谨的吗?我怎么觉得反复无常,飘忽不定呢。还是我根本就没理解作者想要说的是什么?
132 |
133 | logback 内置了一个名为 [JNDIBasedContextDiscriminator](https://logback.qos.ch/xref/ch/qos/logback/classic/sift/JNDIBasedContextDiscriminator.html) 的鉴别器。它可以返回由 `ContextJNDISelector` 计算得来的当前 logger 上下文的名字。`SiftingAppender` 与 `JNDIBasedContextDiscriminator` 结合使用将会为每个 web 应用创建一个单独的 appender。
134 |
135 | ```xml
136 |
137 |
138 |
139 |
140 |
141 |
142 | unknown
143 |
144 |
145 |
146 | ${contextName}.log
147 |
148 | %-50(%level %logger{35}) cn=%contextName - %msg%n
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | ```
159 |
160 | 如果 kenobi 与 yoda 都是 web 应用,那么上面的配置将会把 yoda 的日志输出到 *yoda.log*,kenobi 的日志输出到 *kenobi.log* 中。这甚至适用共享类中静态 logger 生成的日志。
161 |
162 | 你可以通过 [logback-starwars](http://github.com/ceki/logback-starwars) 这个项目来对这项技术进行尝试。
163 |
164 | 上面这个方法解决了日志隔离的问题,但是相对比较复杂。它需要合理的使用 `ContextJNDISelector` 以及通过 `SiftingAppender` 包裹 appender 进行托管。`SiftingAppender` 本身就不是一个平凡的东西。
165 |
166 | 每个日志上下文都可以通过同个文件或者不同的文件进行配置。选择权在于你。让所有的上下文使用同一份配置文件会更加简单,因为只有一份文件需要去维护。为每个应用维护一个配置文件会更难维护,但是会更加的灵活。
167 |
168 | 我们已经完成了吗?可以宣布胜利,然后回家了吗?不全是。
169 |
170 | 假设 web 应用 `yoda` 已经在 `kenobi` 之前被初始化。为了初始化 `yoda`,访问 `http://localhost:port/yoda/servlet` 将会调用 `YodaServlet`。这个 servlet 仅仅只会说 hello,以及在调用 `Mustafar` 中的 `foo` 方法之前打印消息,不必感到奇怪,它只是简单的打印消息,然后返回。
171 |
172 | 在 `YodaServlet` 被调用后,*yoda.log* 会包含如下内容:
173 |
174 | ```java
175 | DEBUG ch.qos.starwars.yoda.YodaServlet cn=yoda - in doGet()
176 | DEBUG ch.qos.starwars.shared.Mustafar cn=yoda - in foo()
177 | ```
178 |
179 | 注意两个日志条目是如何与 "yoda" 上下文的名字相关联的。logger `ch.qos.starwars.shared.Mustafar` 会一直附加在 'yoda' 上下文中,直到服务被停止。
180 |
181 | 访问 `http://localhost:port/kenobi/servlet` 将会在 *kenobi.log* 中输出:
182 |
183 | ```java
184 | DEBUG ch.qos.starwars.kenobi.KenobiServlet cn=kenobi - in doGet()
185 | DEBUG ch.qos.starwars.shared.Mustafar cn=yoda - in foo()
186 | ```
187 |
188 | 尽管 logger `ch.qos.starwars.shared.Mustafar` 输出日志到 *kenobi.log* 中,但是它仍然是附加在 'yoda' 上。因此,我们有两个日志上下文输出日志到同一个文件。虽然日志隔离可以按照我们的意愿进行,但是 FileAppender 实例并不能安全写入到同一个文件中,除非它们开启 `prudent` 模式。否则的话,目标文件将会被毁坏。
189 |
190 | 下面是在 `prudent` 模式下的配置文件:
191 |
192 | ```xml
193 |
194 |
195 |
196 |
197 |
198 |
199 | unknown
200 |
201 |
202 |
203 | ${contextName}.log
204 | true
205 |
206 | %-50(%level %logger{35}) cn=%contextName - %msg%n
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | ```
217 |
218 | 如果你可以跟上我们目前的讨论,并且你已经尝试过 logback-starwars 这个示例。那么你现在一定是沉迷于日志记录。那么你应该考虑 [专业帮助](http://www.qos.ch/shop/products/professionalSupport)。
--------------------------------------------------------------------------------
/12第十二章:Groovy 配置.md:
--------------------------------------------------------------------------------
1 | 领域特定语言或者 DSL 更加普遍。logback 基于 XML 的配置可以看做 DSL 的实例。由于 XML 的本质,基于 XML 的配置文件变得非常的啰嗦以及臃肿。另外,logback 中的 Joran 有一个相对庞大的代码,用来专门处理基于 XML 的配置文件。Joran 支持一些非常好的特性,例如变量替换,条件处理,以及动态扩展。但是,不但 Joran 非常复杂,而且给用户的体验非常的不好,或者至少不直观。
2 |
3 | 本章叙述基于 Groovy 的 DSL 致力于一致性,直观性,以及非常强大。任何你可以使用 XML 配置的文件,你都可以用更加简短的符号使用 Groovy 来实现。为了帮助你迁移到 Groovy 风格的配置,我们开发了一个[工具](https://logback.qos.ch/translator/asGroovy.html)。
4 |
5 | ## 常规建议
6 |
7 | 一般来说,*logback.groovy* 文件是 Groovy 程序。因为 Groovy 是 Java 的超集,所以无论你在 Java 执行什么配置操作,你都可以在 *logback.groovy* 文件中做同样的事情。但是,在 Java 中,使用变成的方式配置 logback 有点笨重,所以我们增加了一些 logback 特有的扩展来减轻你的负担。我们尝试限制 logback 特有的拓展符号尽量的少。如果你已经熟悉了 Groovy,那么你应该更加容易去读,去理解甚至去写你自己的 *logback.groovy* 文件。那么不熟悉 Groovy 的人依然会发现 *logback.groovy* 中的语法比 *logback.xml* 中的语法更加容易使用。
8 |
9 | *logback.groovy* 文件是 Groovy 程序,具有最小的 logback 特定的拓展。所有常用的 groovy 结构,例如类的导入,变量定义,字符串 (GString) 中包含 \${..} 评估表达式,以及 if-else 语句在 *logback.grooby* 文件中都是可用的。
10 |
11 | ## 自动导入
12 |
13 | **`1.0.10 版本以后`** 为了减少不必要的引用,一些共同的类以及包会被自动导入。因此,只要你只是配置了内置的 appender,layout 等等,你不需要在你的脚本中添加相对应的导入语句。当然,对于默认导入不会涉及到类,你需要自己导入。
14 |
15 | 下面是默认导入的列表:
16 |
17 | - import ch.qos.logback.core.*;
18 | - import ch.qos.logback.core.encoder.*;
19 | - import ch.qos.logback.core.read.*;
20 | - import ch.qos.logback.core.rolling.*;
21 | - import ch.qos.logback.core.status.*;
22 | - import ch.qos.logback.classic.net.*;
23 | - import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
24 |
25 | 另外,`ch.qos.logback.classic.Level` 中的所有常量 (大写) 都会被静态导入,以及小写的别名。也就是说在你的脚本中可以引用 *INFO* 以及 *info*,而不需要使用静态导入语句。
26 |
27 | ## 不再支持 SiftingAppender
28 |
29 | **`1.0.12 版本以后`** 在 groovy 配置文件中不再支持 `SiftingAppender`。但是,如果有需要,可以重新引进。
30 |
31 | ## *logback.groovy* 特定的拓展
32 |
33 | **本质上,*logback.groovy* 语法包含以下所说的六个方法;按照它们习惯上相反的顺序出现。**严格来说,这些方法的调用顺序并**不**重要,但是有一个例外:appender 附加到 logger 之前**必须**被定义。
34 |
35 | - ### root(Level level, List\ appenderNames = [])
36 |
37 | `root` 方法可以用来设置 root logger 的日志级别。第二个可选参数的类型为 `List`,可以用来添加之前定义的 appender 的名字。如果你不想指定 appenderNames,那么就是一个空 (empty) 的列表。在 Groovy 中,用 `[]` 表示一个空的列表。
38 |
39 | 设置 root logger 的级别为 WARN,你可以这样写:
40 |
41 | ```groovy
42 | root(WARN)
43 | ```
44 |
45 | 设置 root logger 的级别为 INFO,并且将名为 "CONSOLE" 与 "FILE" 的 appender 附加到 root 上,你可以这样写:
46 |
47 | ```groovy
48 | root(INFO, ["CONSOLE", "FILE"])
49 | ```
50 |
51 | 在前面的例子中,假设名为 "CONSOLE" 与 "FILE" 的 appender 已经被定义好了。很快将会讨论有关 appender 的定义。
52 |
53 | - ### logger(String name, Level level, List\ appenderNames = [], Boolean additivity = null)
54 |
55 | `logger()` 方法接收四个参数,最后两个是可选的。第一个参数表示配置 logger 的名字。第二参数表示指定 logger 的级别。设置 logger 的级别为 `null` 将强制它从它最近的祖先那里[继承](https://github.com/Volong/logback-chinese-manual/blob/master/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%EF%BC%9A%E6%9E%B6%E6%9E%84.md#%E6%9C%89%E6%95%88%E7%AD%89%E7%BA%A7%E5%8F%88%E7%A7%B0%E4%B8%BA%E7%AD%89%E7%BA%A7%E7%BB%A7%E6%89%BF)级别。第三个参数的类型为 `List`,是可选的,默认为空列表。列表中 appender 会被附加到指定的 logger 上去。第四个参数的类型为 `Boolean`,也是可选的,用来控制[叠加性](https://github.com/Volong/logback-chinese-manual/blob/master/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%EF%BC%9A%E6%9E%B6%E6%9E%84.md#appender-%E4%B8%8E-layout)。如果忽略,默认值为 `null`。
56 |
57 | 例如,下面这个脚本设置 "com.foo" 这个 logger 的级别为 INFO:
58 |
59 | ```groovy
60 | logger("com.foo", INFO)
61 | ```
62 |
63 | 下个脚本设置 "com.foo" 这个 logger 的级别为 DEBUG,并且将名为 "CONSOLE" 的 appender 附加到其上:
64 |
65 | ```groovy
66 | logger("com.foo", DEBUG, ["CONSOLE"])
67 | ```
68 |
69 | 下个脚本跟上一个类似,只是这个还设置了 "com.foo" 这个 logger 的叠加性为 false:
70 |
71 | ```groovy
72 | logger("com.foo", DEBUG, ["CONSOLE"],false)
73 | ```
74 |
75 | - ### appender(String name, Class clazz, Closure closure = null)
76 |
77 | appender 方法的第一个参数接收 appender 的名字进行配置。第二个参数是强制的,表示 appender 实例化的类。第三个参数包含更多的配置信息。如果忽略,默认为 null。
78 |
79 | 大部分 appender 都需要设置属性,并且注入子组件才能正常工作。属性通过 '=' 进行设置。子组件的注入通过调用以属性命名的方法,并且将实例化的类作为参数传递给该方法。这个约定可以被递归的应用到配置的属性以及任何 appender 子组件的子组件中。这个方法是 *logback.groovy* 的核心,可能是唯一需要去学习的约定。
80 |
81 | 例如,接下来的脚本实例化一个 `FileAppender` 命名为 "FILE",设置它的 `file` 属性为 "testFile.log",以及它的 `append` 属性设置为 false。类型为 `PatternLayoutEncoder` 的 encoder 被注入到这个 appender 中。encoder 的模式属性设置为 "%level %logger - %msg%n"。然后将这个 appender 附加到 root logger 上。
82 |
83 | ```groovy
84 | appender("FILE", FileAppender) {
85 | file = "testFile.log"
86 | append = false
87 | encoder(PatternLayoutEncoder) {
88 | pattern = "%level %logger - %msg%n"
89 | }
90 | }
91 |
92 | root(DEBUG, ["FILE"])
93 | ```
94 |
95 | - ### timestamp(String datePattern, long timeReference = -1)
96 |
97 | `timestamp()` 方法根据 `datePattern` 将 `timeReference` 参数格式化,返回一个对应的字符串。`datePattern` 参数应该尊村 [SimpleDateFormat](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html) 中定义的约定。如果 `timeReference` 没有指定,那么默认为 -1。在这种情况下,当解析配置文件时,当前时间作为 `timeReference` 参数的值。
98 |
99 | 在下个例子中,`bySecond` 变量表示被 "yyyyMMdd'T'HHmmss" 格式化之后的当前时间。之后,"bySecond" 变量被用于 `file` 属性的定义中。
100 |
101 | ```groovy
102 | def bySecond = timestamp("yyyyMMdd'T'HHmmss")
103 |
104 | appender("FILE", FileAppender) {
105 | file = "log-${bySecond}.txt"
106 | encoder(PatternLayoutEncoder) {
107 | pattern = "%logger{35} - %msg%n"
108 | }
109 | }
110 |
111 | root(DEBUG, ["FILE"])
112 | ```
113 |
114 | - ### conversionRule(String conversionWord, Class converterClass)
115 |
116 | 在创建了你自己的[转换说明符](https://github.com/Volong/logback-chinese-manual/blob/master/06%E7%AC%AC%E5%85%AD%E7%AB%A0%EF%BC%9ALayouts.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BD%AC%E6%8D%A2%E8%AF%B4%E6%98%8E%E7%AC%A6)之后,你需要通知 logback 它的存在。下面这个简单的 logback.groovy 文件告诉 logback 在遇到 `%sample` 转换字符时使用 MySampleConverter。
117 |
118 | ```groovy
119 | import chapters.layouts.MySampleConverter
120 |
121 | conversionRule("sample", MySampleConverter)
122 | appender("STDOUT", ConsoleAppender) {
123 | encoder(PatternLayoutEncoder) {
124 | pattern = "%-4relative [%thread] %sample - %msg%n"
125 | }
126 | }
127 |
128 | root(DEBUG, ["STDOUT"])
129 | ```
130 |
131 | - ### scan(String scanPeriod = null)
132 |
133 | 调用 scan() 方法告诉 logback 周期性的扫描 logback.groovy 文件的变化。当检测到变化时,*logback.groovy* 文件会被重新加载。
134 |
135 | ```groovy
136 | scan()
137 | ```
138 |
139 | 默认情况下,一分钟扫描一次配置文件。你可以通过 "scanPeriod" 来指定一个不同的扫描周期。它的值可以被指定以 milliseconds, seconds, minutes 或者 hours 位单位。例如:
140 |
141 | ```groovy
142 | scan("30 seconds")
143 | ```
144 |
145 | 如果没有指定时间单位,那么默认的时间单位为 milliseconds,但是通常来说是不合适的 (既然不合适,为什么默认还是毫秒,费解🤔)。如果你更改了默认的扫描周期,记得要指定时间单位。更多关于扫描工作的细节,请查看[自动加载](https://github.com/Volong/logback-chinese-manual/blob/master/03%E7%AC%AC%E4%B8%89%E7%AB%A0%EF%BC%9Alogback%20%E7%9A%84%E9%85%8D%E7%BD%AE.md#%E5%BD%93%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%9B%B4%E6%94%B9%E6%97%B6%E8%87%AA%E5%8A%A8%E5%8A%A0%E8%BD%BD)部分。
146 |
147 | - ### statusListener(Class listenerClass)
148 |
149 | 你可以通过调用 `statusListener` 方法,并给该方法传递一个监听器类,来添加一个状态监听器。例:
150 |
151 | ```groovy
152 | import chapters.layouts.MySampleConverter
153 |
154 | // 强烈建议在最后一个导入语句之后,其它所有语句之前添加状态监听器
155 | statusListener(OnConsoleStatusListener)
156 | ```
157 |
158 | 关于[状态监听器](https://github.com/Volong/logback-chinese-manual/blob/master/03%E7%AC%AC%E4%B8%89%E7%AB%A0%EF%BC%9Alogback%20%E7%9A%84%E9%85%8D%E7%BD%AE.md#%E7%9B%91%E5%90%AC%E7%8A%B6%E6%80%81%E4%BF%A1%E6%81%AF)请查看之前的章节。
159 |
160 | - ### jmxConfigurator(String name)
161 |
162 | 你可以通过该方法注册一个 [`JMXConfigurator`](https://logback.qos.ch/manual/jmxConfig.html) MBean。无参调用将会使用 logback 默认的对象名 (`ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator`) 去注册 MBean。
163 |
164 | ```groovy
165 | jmxConfigurator()
166 | ```
167 |
168 | 要改变 `Name` 键的值,而不是 "default",仅仅只需要给 `jmxConfigurator` 方法传递一个不同的名字参数就可以了。
169 |
170 | ```groovy
171 | jmxConfigurator('MyName')
172 | ```
173 |
174 | 如果你想要完整的定义对象名,可以使用同样的语法,但是需要传递一个有效的对象名字符串作为参数:
175 |
176 | ```groovy
177 | jmxConfigurator('myApp:type=LoggerManager')
178 | ```
179 |
180 | 该方法首先会去尝试将该参数作为对象名,如果它不表示一个有效的对象名,则会把它当作 "Name" 键的值。
181 |
182 | ## 内置 DSL
183 |
184 | *logback.groovy* 是一个内置 DSL 的意思是,它的内容可以作为 Groovy 脚本执行。因此,所有常用的 Groovy 指令,例如类的导入,GString,变量的定义,包含字符串 (GString) 的 \${..} 评估表达式,if-else 语句这些在 logback.groovy 文件中都是可用的。在接下来的讨论中,我们将会展示 Groovy 指令在 *logback.groovy* 文件中的典型用法。
185 |
186 | ### 变量定义与 GString
187 |
188 | 你可以在 *logback.groovy* 文件中的任何地方定义变量,然后在 GString 中使用该变量。例如:
189 |
190 | ```groovy
191 | def USER_HOME = System.getProperty("user.home")
192 |
193 | appender("FILE", FileAppender) {
194 | // 使用 USER_HOME 变量
195 | file = "${USER_HOME}/myApp.log"
196 | encoder(PatternLayoutEncoder) {
197 | pattern = "%msg%n"
198 | }
199 | }
200 | root(DEBUG, ["FILE"])
201 | ```
202 |
203 | ### 在控制台打印
204 |
205 | 通过调用 Groovy 的 `println` 方法在控制台进行打印。例如:
206 |
207 | ```groovy
208 | def USER_HOME = System.getProperty("user.home");
209 | println "USER_HOME=${USER_HOME}"
210 |
211 | appender("FILE", FileAppender) {
212 | println "Setting [file] property to [${USER_HOME}/myApp.log]"
213 | file = "${USER_HOME}/myApp.log"
214 | encoder(PatternLayoutEncoder) {
215 | pattern = "%msg%n"
216 | }
217 | }
218 | root(DEBUG, ["FILE"])
219 | ```
220 |
221 | ### 自动输出字段
222 |
223 | #### 'hostname' 变量
224 |
225 | 'hostname' 变量包含当前 host 的名字。但是由于作用域规则,所以作者不能完全解释清楚 (😓)。'hostname' 变量只在最上层的作用域中有效,但是在内部的作用域中无效。下面的例子应该可以解释这一点:
226 |
227 | ```groovy
228 | // 如果当前 host 的名字为 x,那么将会输出 "hostname is x"
229 | println "hostname is ${hostname}"
230 |
231 | appender("STDOUT", ConsoleAppender) {
232 | // 将会输出 "hostname is null"
233 | println "hostname is ${hostname}"
234 | }
235 | ```
236 |
237 | 如果你想要在所有的作用域中使用 hostname 变量。那么你需要定义一个变量,并将 'hostname' 的值赋给它。如下:
238 |
239 | ```groovy
240 | // 将 hostname 的值赋给 HOSTNAME
241 | def HOSTNAME = hostname
242 |
243 | // 如果当前 host 的名字为 x,那么将会输出 "hostname is x"
244 | println "hostname is ${HOSTNAME}"
245 |
246 | appender("STDOUT", ConsoleAppender) {
247 | // 如果当前 host 的名字为 x,那么将会输出 "hostname is x"
248 | println "hostname is ${HOSTNAME}"
249 | }
250 | ```
251 |
252 | ### 任何对于当前上下文的引用都是上下文感知的
253 |
254 | *logback.groovy* 脚本是在 [ContextAware](https://logback.qos.ch/xref/ch/qos/logback/core/spi/ContextAware.html) 对象的范围内执行完成的。因此,在当前上下文的范围内,你可以使用 '`context`',并且可以通过 `addInfo()`、`addWarn()`、与 `addError()` 方法将状态信息发送给上下文的 `StatusManager`。
255 |
256 | ```groovy
257 | // 添加一个控制台转态监听器总是没错的
258 | statusListener(OnConsoleStatusListener)
259 |
260 | // 设置上下文的名字为 wombat
261 | context.name = 'wombat'
262 |
263 | // 添加一个关于上下文名字的状态信息
264 | addInfo("Context name has been set to ${context_name}")
265 |
266 | def USER_HOME = System.getProperty("user.home")
267 |
268 | // 添加关于 USRE_HOME 的状态信息
269 | addInfo("USER_HOME=${USER_HOME}")
270 |
271 | appender("FILE", FileAppender) {
272 | addInfo("Setting [file] property to [${USER_NAME}/myApp.log]")
273 | file = "${USER_HOME}/myApp.log"
274 | encoder(PatternLayoutEncoder) {
275 | pattern = "%msg%n"
276 | }
277 | }
278 | root(DEBUG, ["FILE"])
279 | ```
280 |
281 | ### 条件配置
282 |
283 | 由于 Groovy 是一种完全成熟的编程语言,条件语句允许单一的 *logback.groovy* 文件用来适用不同的环境,例如开发,测试以及生产。
284 |
285 | 在下个脚本中,console appender 根据 host 来激活,而不是我们的生产环境 pixie 或 orion。rolling file appender 的输出目录也是根据 host 来确定。
286 |
287 | ```groovy
288 | statusListener(OnConsoleStatusListener)
289 |
290 | def appenderList = ["ROLLING"]
291 | def WEBAPP_DIR = "."
292 | def consoleAppender = true;
293 |
294 | // hostname 是否匹配 pixie 或 orion
295 | if (hostname =~ /pixie|orion/) {
296 | WEBAPP_DIR = "/opt/myapp"
297 | consoleAppender = false
298 | } else {
299 | appenderList.add("CONSOLE")
300 | }
301 |
302 | if (consoleAppender) {
303 | appender("CONSOLE", ConsoleAppender) {
304 | encoder(PatternLayoutEncoder) {
305 | pattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
306 | }
307 | }
308 | }
309 |
310 | appender("ROLLING", RollingFileAppender) {
311 | encoder(PatternLayoutEncoder) {
312 | Pattern = "%d %level %thread %mdc %logger - %m%n"
313 | }
314 | rollingPolicy(TimeBasedRollingPolicy) {
315 | FileNamePattern = "${WEBAPP_DIR}/log/translator-%d{yyyy-MM}.zip"
316 | }
317 | }
318 |
319 | root(INFO, appenderList)
320 | ```
321 |
322 |
--------------------------------------------------------------------------------
/14第十四章:Receivers.md:
--------------------------------------------------------------------------------
1 | ## 什么是 Receiver
2 |
3 | *receiver* 是 logback 的一个组件,用于接收远程 appender 的日志事件,根据本地策略打印接收到的日志事件。结合使用基于套接字的 appender 与 receiver,可以构建复杂的拓扑图,通过网络分发应用程序的日志事件。
4 |
5 | 一个 receiver 继承 [`ch.qos.logback.classic.net.ReceiverBase`](https://logback.qos.ch/xref/ch/qos/logback/classic/net/ReceiverBase.html) 类。由于 receiver 继承了这个类,所以它也是 logback 组件 [LifeCycle](https://logback.qos.ch/xref/ch/qos/logback/core/spi/LifeCycle.html) 的一部分,而且它也是一个 [ContextAware](https://logback.qos.ch/xref/ch/qos/logback/core/spi/ContextAware.html)。
6 |
7 | 过去为了支持日志事件通过网络传输,logback 提供了 `SocketAppender` 与 `SimpleSocketServer`。appender 充当一个客户端,初始化到服务器应用程序的网络连接,再通过网络传输日志事件。receiver 组件以及相应的 appender 提供了很大的灵活性。在 *logback.xml* 中配置一个 receiver 组件,跟配置其它的组件一样。这允许配置 receiver 组件利用 [Joran](https://logback.qos.ch/manual/onJoran.html) 所有的功能。而且,任何应用都可以通过简单的配置一个或多个 receiver 组件从远程 appender 接收日志事件。
8 |
9 | 连接开始可以发生在 appender 与 recevier 之间的任何一方。一个 receiver 可以充当服务器的角色,被动的监听远程 appender 客户端的连接。或者,receiver 充当客户端的角色,初始化与远程 appender 服务器的连接。不管 appender 与 receiver 的角色是什么,*日志事件总是从 appender 流向 receiver。*
10 |
11 | 允许 receiver 初始化到 appender 的连接的灵活性在特定的情况下特别的有用:
12 |
13 | - 由于安全的原因,中心日志服务器位于网络防火墙的后面,不允许即将到来的连接。使用 receiver 组件充当客户端的角色,中心日志服务器 (防火墙内) 可以初始化对应用程序 (防火墙外) 的连接。
14 | - 开发者工具与企业管理应用可以获取正在运行中的程序的日志事件流。通常,logback 通过要求接收方应用程序 (例如,IDE 中正在运行的开发者工具) 去充当服务器的角色,被动的监听来自远程 appender 的连接来支持这个 (例如在 logback Beagle 中)。这提高了管理的困难,特别是当工具运行在开发者的工作站中的时候,有可能是通过移动设备。但是,可以使用 logback receiver 组件充当客户端角色来实现这些工具。初始化一个到远程 appender 的连接,接收日志事件用于本地显示,过滤以及报警。
15 |
16 | 一个 logback 的配置可以包含任何数量的 receiver 组件,充当任意组合的服务器或者客户端的角色。唯一的限制是每个充当服务器角色的 receiver 必须监听在不同的端口,每个充当客户端角色的 receiver 将会准备的连接到一个远程的 appender 上。
17 |
18 | ## Receivers 充当服务器角色
19 |
20 | 一个 receiver 可以被配置为充当服务器的绝对,被动的监听远程 appender 即将到来的连接。这个功能类似于使用独立的 `SimpleSocketServer` 应用,特别是在使用 receiver 组件时,*任何* 应用仅仅只需要在 *logback.xml* 中配置 receiver 就可以使用 logback-classic 接收来自远程 appender 的日志事件。
21 |
22 | 
23 |
24 | logback 包含两个 receiver 组件用于充当服务器角色。[`ServerSocketReceiver`](https://logback.qos.ch/xref/ch/qos/logback/classic/net/server/ServerSocketReceiver.html) 以及它支持 SSL 的子类型 [`SSLServerSocketReceiver`](https://logback.qos.ch/xref/ch/qos/logback/classic/net/server/SSLServerSocketReceiver.html)。这两个 receiver 组件都被设计成用来接收来自于 `SocketAppender` (或者 `SSLSocketAppender`) 客户端的连接。
25 |
26 | `ServerSocketReceiver` 组件提供以下配置属性:
27 |
28 | | 属性名 | 类型 | 描述 |
29 | | ----------- | ------------------ | ------------------------------------------------------------ |
30 | | **address** | `String` | receiver 监听的本地网络接口。如果没有指定这个属性,那么将监听所有的网络接口。 |
31 | | **port** | `int` | receiver 监听的 TCP 端口。如果这个属性没有被指定,将会使用一个默认的值。(译者注:默认的端口为 4560) |
32 | | **ssl** | `SSLConfiguration` | 仅仅支持 `SSLServerSocketReceiver`。这个属性提供了 receiver 使用的 SSL 配置。详情请见 [第十五章](https://logback.qos.ch/manual/usingSSL.html) |
33 |
34 | ### 使用 ServerSocketReceiver
35 |
36 | 下面的配置使用 `ServerSocketReceiver` 组件,它使用最小的本地 appender 以及 logger 配置。接收来自远程 appender 的日志事件,将会匹配到 root logger,并输出到本地的 console appender。
37 |
38 | > Example: receiver1.xml
39 |
40 | ```xml
41 |
42 |
43 |
44 |
45 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | ${port}
55 |
56 |
57 |
58 | ```
59 |
60 | receiver 组件的 *class* 属性表明了我们想要使用的 receiver 子类型。在这个例子中,我们使用 `ServerSocketReceiver`。
61 |
62 | 我们示例中的服务器应用在功能与设计上与 `SimpleSocketServer` 非常的类似。它仅仅接收 logback 配置文件的路径作为命令行的参数,并运行给定的配置。虽然我们的示例程序很简单,但是请记住,你可以在*任何*应用中配置 logback 的 `ServerSocketReceiver` (或者 `SSLServerSocketReceiver`) 组件。
63 |
64 | 我们可以在 *logback-examples* 文件夹中通过以下方式运行示例中服务器应用:
65 |
66 | ```shell
67 | java -Dport=6000 chapters.receivers.socket.ReceiverExample \
68 | src/main/java/chapters/receivers/socket/receiver1.xml
69 | ```
70 |
71 | 我们可以使用配置了 `SocketAppender` 的客户端应用来连接运行中的 receiver。我们示例当中的客户端应用仅仅加载了 logback 配置,就可以连接我们示例中 receiver 的 socket appender。然后,它会等待用户的数据,以消息的形式转发给 receiver。我们可以通过如下的方式来运行客户端应用示例:
72 |
73 | ```shell
74 | java -Dhost=localhost -Dport=6000 \
75 | chapters.receivers.socket.AppenderExample \
76 | src/main/java/chapters/receivers/socket/appender1.xml
77 | ```
78 |
79 | ### 使用 SSLServerSocketReceiver
80 |
81 | 下面的配置重复使用了最小的 appender 以及 logger 配置,但是使用了支持 SSL 的 receiver 组件,用于充当服务器角色。
82 |
83 | > Example: receiver2.xml
84 |
85 | ```xml
86 |
87 |
88 |
89 |
90 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | ${port}
100 |
101 |
102 | ${keystore}
103 | ${password}
104 |
105 |
106 |
107 |
108 |
109 | ```
110 |
111 | 这个配置与之前使用 `ServerSocketReceiver` 的示例本质的区别是通过 *class* 属性指定了 `SSLServerSocketReceiver` 以及内嵌的 `ssl` 属性,使用变量替换来指定 key store 中包含 receiver 的私钥以及证书的位置以及密码。关于 logback 组件配置 SSL 属性的详细信息请查看[第十五章](https://logback.qos.ch/manual/usingSSL.html)。
112 |
113 | 我们可以使用相同的示例服务器配置来运行这个配置,仅仅添加几个额外的配置属性:
114 |
115 | ```shell
116 | java -Dport=6001 \
117 | -Dkeystore=file:src/main/java/chapters/appenders/socket/ssl/keystore.jks \
118 | -Dpassword=changeit \
119 | chapters.receivers.socket.ReceiverExample \
120 | src/main/java/chapters/receivers/socket/receiver2.xml
121 | ```
122 |
123 | *keystore* 属性被用来在命令行指定文件 URL 来标识 key store 的位置。你也可以使用[第十五章](https://logback.qos.ch/manual/usingSSL.html)所说的类路径 URL。
124 |
125 | 我们可以使用配置了 `SSLSocketAppender` 的客户端应用来连接运行中的 receiver。我们使用在之前示例中使用过的简单示例客户端应用,通过一个开启了 SSL appender 的配置文件,以如下方式运行:
126 |
127 | ```shell
128 | java -Dhost=localhost -Dport=6001 \
129 | -Dtruststore=file:src/main/java/chapters/appenders/socket/ssl/truststore.jks \
130 | -Dpassword=changeit \
131 | chapters.receivers.socket.AppenderExample \
132 | src/main/java/chapters/receivers/socket/appender2.xml
133 | ```
134 |
135 | 注意,在我们的示例中,使用了自签名的 X.509 证书,这仅仅适用于测试。**在生产环境中的设置中,你应用获取一个合适的 X.509 证书,用于标识你的开启了 SSL 支持的 logback 组件。**查看[第十五章](https://logback.qos.ch/manual/usingSSL.html)获取更多的信息。
136 |
137 | ## Receivers 充当客户端角色
138 |
139 | 配置 receiver 充当客户端角色,初始化一个到远程 appender 的连接。远程 appender 必须是服务器类型,例如 `ServerSocketAppender`。
140 |
141 | 
142 |
143 |
144 |
145 | logback 包含两个 receiver 组件用于充当客户端角色:[`SocketReceiver`](https://logback.qos.ch/xref/ch/qos/logback/classic/net/SocketReceiver.html) 以及它的支持 SSL 子类型的 [`SSLSocketReceiver`](https://logback.qos.ch/xref/ch/qos/logback/classic/net/SSLSocketReceiver.html)。这两个组件都被设计成初始化一个连接到远程 appender,也就是 `ServerSocketAppender` (或者 `SSLServerSocketAppender`)。
146 |
147 | `SocketReceiver` 子类型支持如下的配置属性:
148 |
149 | | 属性名 | 类型 | 描述 |
150 | | --------------------- | ------------------ | ------------------------------------------------------------ |
151 | | **remoteHost** | `String` | 远程服务器 socket appender 的 hostname 或者地址 |
152 | | **port** | `int` | 远程服务器 socket appender 的端口号 |
153 | | **reconnectionDelay** | `int` | 在某次连接失败之后,在尝试重连时的等待时间。默认值为 3000 (30 秒)。 |
154 | | **ssl** | `SSLConfiguration` | 仅仅支持 `SSLSocketReceiver`,这个属性提供了用于这个 receiver 的 SSL 配置。如[第十五章](https://logback.qos.ch/manual/usingSSL.html)描述的那样。 |
155 |
156 | ### 使用 SocketReceiver
157 |
158 | 用于 `SocketReceiver` 的配置跟之前使用 `ServerSocketReceiver` 的示例非常的相似。不同的地方在于客户端与服务端的角色反转了。`SocketReceiver` 类型的 receiver 为客户端,远程 appender 充当服务器的角色。
159 |
160 | > Example: receiver3.xml
161 |
162 | ```xml
163 |
164 |
165 |
166 |
167 | %date %-5level [%thread] %logger - %message%n
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | ${host}
177 | ${port}
178 | 10000
179 |
180 |
181 |
182 | ```
183 |
184 | 这个配置将会使 logback 连接通过 *host* 与 *port* 变量替换指定的主机与端口上的 `ServerSocketAppender`。将会通过 console appender 本地输出 (根据这里的配置文件) 从远程 appender 接收到的日志事件。
185 |
186 | 你可以在 *logback-examples/* 文件夹下,通过以下命令运行示例中的配置文件:
187 |
188 | 这个示例仅仅加载配置文件,然后仅仅等待来自远程 appender 的日志事件。如果你在远程 appender 没有运行的情况下运行这个示例,那么你将会周期性的看到*连接被拒绝*的日志消息输出。receiver 将会周期性的尝试重新连接远程 appender,直到连接成功或者 logger 上下文关闭。尝试的延迟间隔是可以通过 `reconnectionDelay` 属性来配置的,如示例配置中展示的一样。
189 |
190 | ```shell
191 | java -Dhost=localhost -Dport=6000 \
192 | chapters.receivers.socket.ReceiverExample \
193 | src/main/java/chapters/receivers/socket/receiver3.xml
194 | ```
195 |
196 | 我们示例中的 receiver 连接之前使用过的同一个远程 apennder。这个示例加载一个包含 `ServerSocketAppender` 的配置,然后等待用户的输入,输入的消息将会被传递给已经连接上的 receiver。我们可以通过如下方式运行示例 appender 应用:
197 |
198 | ```shell
199 | java -Dport=6000 \
200 | chapters.receivers.socket.AppenderExample \
201 | src/main/java/chapters/receivers/socket/appender3.xml
202 | ```
203 |
204 | 如果在 receiver 没有连接上的情况下输入消息,那么消息将会被丢弃。
205 |
206 | ### 使用 SocketSSLReceiver
207 |
208 | `SSLSocketReceiver` 需要的配置跟使用 `SocketReceiver` 非常的类似。本质的区别在于通过 class 指定的 receiver,以及通过内嵌的 `ssl` 属性去指定 SSL 配置属性。下面是一个基础的示例:
209 |
210 | > Example: receiver4.xml
211 |
212 | ```xml
213 |
214 |
215 |
216 |
217 | %date %-5level [%thread] %logger - %message%n
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | ${host}
227 | ${port}
228 | 10000
229 |
230 |
231 | ${truststore}
232 | ${password}
233 |
234 |
235 |
236 |
237 |
238 | ```
239 |
240 | 除了在上一个例子中展示的配置属性之外,*class* 属性现在指定了 `SSLSocketReceiver`。配置文件中包含了指定 trust strore 的位置与密码的 SSL 配置。用于验证远程 appender 是可以受信任的。查看[第十五章](https://logback.qos.ch/manual/usingSSL.html)获取更多关于配置 SSL 属性的信息。
241 |
242 | 通过如下命令来运行示例配置:
243 |
244 | ```shell
245 | java -Dhost=localhost -Dport=6001 \
246 | -Dtruststore=file:src/main/java/chapters/appenders/socket/ssl/truststore.jks \
247 | -Dpassword=changeit \
248 | chapters.receivers.socket.ReceiverExample \
249 | src/main/java/chapters/receivers/socket/receiver4.xml
250 | ```
251 |
252 | 一旦启动,receiver 尝试去连接指定的远程 appender。如果 appender 没有运行,那么你将会在日志输出中周期性的看到 "连接被拒" 的信息。在延迟了通过 `reconnectionDelay ` 指定的周期时间后,receiver 将会周期性的重试到远程 appender 的连接。
253 |
254 | 我们示例中的 receiver 会连接之前使用过的远程 appender。这个示例加载包含了 `SSLServerSocketAppender` 的配置,然后等待用户的输入,输入的信息将会被传递给连接上的 receiver。通过如下方式运行示例 appender 应用:
255 |
256 | ```shell
257 | java -Dport=6001 \
258 | -Dkeystore=file:src/main/java/chapters/appenders/socket/ssl/keystore.jks \
259 | -Dpassword=changeit \
260 | chapters.receivers.socket.AppenderExample \
261 | src/main/java/chapters/receivers/socket/appender4.xml
262 | ```
263 |
264 | 如果在 receiver 没有连接上的时候输入信息,那么信息将会被丢弃。
265 |
266 | 需要再次注意的是,我们的示例使用的是仅适用于测试的自签名 X.509 证书。**在生产环境中,你应该获取适当的 X.509 证书来标识你的支持 SSL 的 logback 组件。**更多细节请查看[第十五章](https://logback.qos.ch/manual/usingSSL.html)。
--------------------------------------------------------------------------------
/02第二章:架构.md:
--------------------------------------------------------------------------------
1 | ## logback 的架构
2 |
3 | 跟[简介](https://github.com/Volong/logback-chinese-manual#%E7%AE%80%E4%BB%8B)类似
4 |
5 | ## Logger, Appender 和 Layouts
6 |
7 | Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。
8 |
9 | `Logger` 类作为 logback-classic 模块的一部分。`Appender` 与 `Layouts` 接口作为 logback-core 的一部分。作为一个通用的模块,logback-core 没有 logger 的概念。
10 |
11 | ### Logger 上下文
12 |
13 | 任何日志 API 的优势在于它能够禁止某些日志的输出,但是又不会妨碍另一些日志的输出。通过假定一个日志空间,这个空间包含所有可能的日志语句,这些日志语句根据开发人员设定的标准来进行分类。在 logback-classic 中,分类是 logger 的一部分,每一个 logger 都依附在 `LoggerContext` 上,它负责产生 logger,并且通过一个树状的层级结构来进行管理。
14 |
15 | 一个 Logger 被当作为一个实体,它们的命名是大小写敏感的,并且遵循以下规则:
16 |
17 | > 命名层次结构
18 | >
19 | > 如果一个 logger 的名字加上一个 `.` 作为另一个 logger 名字的前缀,那么该 logger 就是另一个 logger 的祖先。如果一个 logger 与另一个 logger 之间没有其它的 logger ,则该 logger 就是另一个 logger 的父级。
20 |
21 | 例如:名为 `com.foo` 的 logger 是名为 `com.foo.Bar` 的 logger 的父级。名为 `java` 的 logger 是名为 `java.util` 的父级,是名为 `java.util.Vector` 的祖先。
22 |
23 | root logger 作为 logger 层次结构的最高层。它是一个特殊的 logger,因为它是每一个层次结构的一部分。每一个 logger 都可以通过它的名字去获取。例:
24 |
25 | ```java
26 | Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME)
27 | ```
28 |
29 | 所有其它的 logger 通过 `org.slf4j.LoggerFactory` 类的静态方法 `getLogger` 去获取,这个方法需要传入一个 logger 的名字。下面是 `Logger` 接口一些基本的方法:
30 |
31 | ```java
32 | package org.slf4j;
33 | public interface Logger {
34 | public void trace(String message);
35 | public void debug(String message);
36 | public void info(String message);
37 | public void warn(String message);
38 | public void error(String message);
39 | }
40 | ```
41 |
42 | ### 有效等级又称为等级继承
43 |
44 | Logger 能够被分成不同的等级。不同的等级(TRACE, DEBUG, INFO, WARN, ERROR)定义在 `ch.qos.logback.classic.Level` 类中。在 logback 中,类 `Level` 使用 final 修饰的,所以它不能用来被继承。一种更灵活的方式是使用 `Marker` 对象。
45 |
46 | 如果一个给定的 logger 没有指定一个层级,那么它就会继承离它最近的一个祖先的层级。更正式的说法是:
47 |
48 | > 对于一个给定的名为 *L* 的 logger,它的有效层级为从自身一直回溯到 root logger,直到找到第一个不为空的层级作为自己的层级。
49 |
50 | 为了确保所有的 logger 都有一个层级,root logger 会有一个默认层级 --- DEBUG
51 |
52 | 以下四个例子指定不同的层级,以及根据继承规则得到的最终有效层级
53 |
54 | *Example 1*
55 |
56 | | logger 的名字 | 指定的层级 | 有效层级 |
57 | | :-----------: | :--------: | :------: |
58 | | root | DEBUG | DEBUG |
59 | | X | none | DEBUG |
60 | | X.Y | none | DEBUG |
61 | | X.Y.Z | none | DEBUG |
62 |
63 | 在这个例子中,只有 root logger 被指定了层级,所以 logger **X**,**X.Y**,**X.Y.Z** 的有效层级都是 DEBUG。
64 |
65 | *Example 2*
66 |
67 | | logger 的名字 | 指定的层级 | 有效层级 |
68 | | :-----------: | :--------: | :------: |
69 | | root | ERROR | ERROR |
70 | | X | INFO | INFO |
71 | | X.Y | DEBUG | DEBUG |
72 | | X.Y.Z | WARN | WARN |
73 |
74 | 在这个例子中,每个 logger 都分配了层级,所以有效层级就是指定的层级。
75 |
76 | *Example 3*
77 |
78 | | logger 的名字 | 指定的层级 | 有效层级 |
79 | | :-----------: | :--------: | :------: |
80 | | root | DEBUG | DEBUG |
81 | | X | INFO | INFO |
82 | | X.Y | none | INFO |
83 | | X.Y.Z | ERROR | ERROR |
84 |
85 | 在这个例子中,logger **root**,**X**,**X.Y.Z** 都分别分配了层级。logger **X.Y** 继承它的父级 logger **X**。
86 |
87 | *Example 4*
88 |
89 | | logger 的名字 | 指定的层级 | 有效层级 |
90 | | :-----------: | :--------: | :------: |
91 | | root | DEBUG | DEBUG |
92 | | X | INFO | INFO |
93 | | X.Y | none | INFO |
94 | | X.Y.Z | none | INFO |
95 |
96 | 在这个例子中,logger **root**,**X** 都分配了层级。logger **X.Y**,**X.Y.Z** 的层级继承它们最近的父级 **X**。
97 |
98 |
99 |
100 | ### 方法打印以及基本选择规则
101 |
102 | 根据定义,打印的方法决定的日志的级别。例如:**L** 是一个 logger 实例,`L.info("...")` 的日志级别就是 INFO。
103 |
104 | 如果一条的日志的打印级别大于 logger 的有效级别,该条日志才可以被打印出来。这条规则总结如下:
105 |
106 | > **基本选择规则**
107 | >
108 | > 日志的打印级别为 *p*,Logger 实例的级别为 *q*,如果 *p* >= *q*,则该条日志可以打印出来。
109 |
110 | 这条规则是 logbakc 的核心。各级别的排序为:**TRACE** < **DEBUG** < **INFO** < **WARN** < **ERROR**。
111 |
112 | 在下面的表格中,第一列表示的是日志的打印级别,用 *p* 表示。第一行表示的是 logger 的有效级别,用 *q* 表示。行列交叉处的结果表示由**基本选择规则**得出的结果。
113 |
114 | 
115 |
116 | 例子:
117 |
118 | ```java
119 | package chapters.architecture;
120 |
121 | import org.slf4j.Logger;
122 | import org.slf4j.LoggerFactory;
123 |
124 | import ch.qos.logback.classic.Level;
125 |
126 | public class SelectionRule {
127 |
128 | public static void main(String[] args) {
129 |
130 | // ch.qos.logback.classic.Logger 可以设置日志的级别
131 | // 获取一个名为 "com.foo" 的 logger 实例
132 | ch.qos.logback.classic.Logger logger =
133 | (ch.qos.logback.classic.Logger)LoggerFactory.getLogger("com.foo");
134 | // 设置 logger 的级别为 INFO
135 | logger.setLevel(Level.INFO);
136 |
137 | // 这条日志可以打印,因为 WARN >= INFO
138 | logger.warn("警告信息");
139 | // 这条日志不会打印,因为 DEBUG < INFO
140 | logger.debug("调试信息");
141 |
142 | // "com.foo.bar" 会继承 "com.foo" 的有效级别
143 | Logger barLogger = LoggerFactory.getLogger("com.foo.bar");
144 | // 这条日志会打印,因为 INFO >= INFO
145 | barLogger.info("子级信息");
146 | // 这条日志不会打印,因为 DEBUG < INFO
147 | barLogger.debug("子级调试信息");
148 | }
149 | }
150 | ```
151 |
152 | ### 获取 Logger
153 |
154 | 通过 `LoggerFactory.getLogger()` 可以获取到具体的 logger 实例,名字相同则返回的 logger 实例也相同。
155 |
156 | ```java
157 | Logger x = LoggerFactory.getLogger("wombat");
158 | Logger y = LoggerFactory.getLogger("wombat");
159 | ```
160 |
161 | **x**,**y** 是同一个 logger 对象。
162 |
163 | 可以通过配置一个 logger,然后在其它地方获取,而不需要传递引用。父级 logger 总是优于子级 logger,并且父级 logger 会自动寻找并关联子级 logger,即使父级 logger 在子级 logger 之后实例化。
164 |
165 | logback 环境的配置会在应用初始化的时候完成。最优的方式是通过读取配置文件。
166 |
167 | 在每个类里面通过指定全限定类名为 logger 的名字来实例化一个 logger 是最好也是最简单的方式。因为日志能够输出这个 logger 的名字,所以这个命名策略能够看出日志的来源是哪里。虽然这是命名 logger 常见的策略,但是 logback 不会严格限制 logger 的命名,你完全可以根据自己的喜好来,你开心就好。
168 |
169 | 但是,根据类的全限定名来对 logger 进行命名,是目前最好的方式,没有之一。
170 |
171 | ### Appender 与 Layout
172 |
173 | 有选择的启用或者禁用日志的输出只是 logger 的一部分功能。logback 允许日志在多个地方进行输出。站在 logback 的角度来说,输出目的地叫做 appender。appender 包括console、file、remote socket server、MySQL、PostgreSQL、Oracle 或者其它的数据库、JMS、remote UNIX Syslog daemons 中。
174 |
175 | 一个 logger 可以有多个 appender。
176 |
177 | logger 通过 `addAppender` 方法来新增一个 appender。对于给定的 logger,每一个允许输出的日志都会被转发到该 logger 的所有 appender 中去。换句话说,appender 从 logger 的层级结构中去继承叠加性。例如:如果 root logger 添加了一个 console appender,所有允许输出的日志至少会在控制台打印出来。如果再给一个叫做 ***L*** 的 logger 添加了一个 file appender,那么 ***L*** 以及 ***L*** 的子级 logger 都可以在文件和控制台打印日志。可以通过设置 additivity = false 来改写默认的设置,这样 appender 将不再具有叠加性。
178 |
179 | appender 的叠加性规则如下:
180 |
181 | > appender 的叠加性
182 | >
183 | > logger *L* 的日志输出语句会遍历 *L* 和它的父级中所有的 appender。这就是所谓的 appender 叠加性(appender additivity)
184 | >
185 | > 如果 *L* 的某个上级 logger 为 *P*,且 *P* 设置了 additivity = false,那么 *L* 的日志会在层级在 *L* 到 *P* 之间的所有 logger 的 appender,包括 *P* 本身的 appender 中输出,但是不会在 *P* 的上级 appender 中输出。
186 | >
187 | > logger 默认设置 additivity = true。
188 |
189 | | Logger | Appender | Additivity 标识 | 输出目的地 | 说明 |
190 | | :-------------: | :--------: | :--------------: | :--------------------: | :----------------------------------------------------------: |
191 | | root | A1 | 不适用 | A1 | root logger 为 logger 层级中的最高层,additivity 对它不适用 |
192 | | x | A-x1, A-x2 | True | A1, A-x1, A-x2 | x 与 root 的 appender |
193 | | x.y | 无 | true | A1, A-x1, A-x2 | x 与 root 的 appender |
194 | | x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | x 与 x.y 与 root 的 appender |
195 | | security | A-sec | **false** | A-sec | 因为 additivity = false,所以只有 A-sec 这个 appender |
196 | | security.access | 无 | true | A-sec | 因为它的父级 logger security 设置了 additivity = false,所以只有 A-sec 这一个 appender |
197 |
198 | 通常,用户既想自定义日志的输出地,也想自定义日志的输出格式。通过给 appender 添加一个 *layout* 可以做到。layout 的作用是将日志格式化,而 appender 的作用是将格式化后的日志输出到指定的目的地。**PatternLayout** 能够根据用户指定的格式来格式化日志,类似于 C 语言的 printf 函数。
199 |
200 | 例:PatternLayout 通过格式化串 "%-4relative [%thread] %-5level %logger{32} - %msg%n" 会将日志格式化成如下结果:
201 |
202 | ```java
203 | 176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
204 | ```
205 |
206 | 第一个参数表示程序启动以来的耗时,单位为毫秒。第二个参数表示当前的线程号。第三个参数表示当前日志的级别。第四个参数是 logger 的名字。“-” 之后是具体的日志信息。
207 |
208 | ### 参数化日志
209 |
210 | 考虑到 logback-classic 实现了 SLF4J 的 Logger 接口,一些打印方法可以接收多个传参。这些打印方法的变体主要是为了提高性能以及减少对代码可读性的影响。
211 |
212 | 对于一些 Logger 如下输出日志:
213 |
214 | ```java
215 | logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
216 | ```
217 |
218 | 会产生构建消息参数的成本,是因为需要将整数转为字符串,然后再将字符串拼接起来。但是我们是不需要关心 debug 信息是否被记录(强行曲解作者的意思)。
219 |
220 | 为了避免构建参数带来的损耗,可以在日志记录之前做一个判断,如下:
221 |
222 | ```java
223 | if(logger.isDebugEnabled()) {
224 | logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
225 | }
226 | ```
227 |
228 | 在这种情况下,如果 **logger**没有开启 debug 模式,不会有构建参数带来的性能损耗。换句话说,如果 logger 在 debug 级别,将会有两次性能的损耗,一次是判断是否启用了 debug 模式,一次是打印 debug 日志。在实际应用当中,这种性能上的损耗是可以忽略不计的,因为它所花费的时间小于打印一条日志的时间的 1%。
229 |
230 | #### 更好的选择
231 |
232 | 有一种更好的方式去格式化日志信息。假设 **entry** 是一个 Object 对象:
233 |
234 | ```java
235 | Object entry = new SomeObject();
236 | logger.debug("The entry is {}", entry);
237 | ```
238 |
239 | 只有在需要打印 debug 信息的时候,才会去格式化日志信息,将 '{}' 替换成 entry 的字符串形式。也就是说在这种情况下,如果禁止了日志的打印,也不会有构建参数上的性能消耗。
240 |
241 | 下面两行输出的结果是一样的,但是一旦禁止日志打印,第二个变量的性能至少比第一个变量好上 30 倍。
242 |
243 | ```java
244 | logger.debug("The new entry is " + entry + ".");
245 | logger.debug("The new entry is {}", entry);
246 | ```
247 |
248 | 使用两个参数的例子如下:
249 |
250 | ```java
251 | logger.debug("The new entry is {}, It replaces {}.", entry, oldEntry);
252 | ```
253 |
254 | 如果需要使用三个或三个以上的参数,可以采用如下的形式:
255 |
256 | ```java
257 | Object[] paramArray = {newVal, below, above};
258 | logger.debug("Value {} was inserted between {} and {}.", paramArray);
259 | ```
260 |
261 | ### 底层实现初探
262 |
263 | 在介绍了基本的 logback 组件之后,我们准备介绍一下,当用户调用日志的打印方法时,logback 所执行的步骤。现在我们来分析一下当用户通过一个名为 *com.wombat* 的 logger 调用了 **info()** 方法时,logback 执行了哪些步骤。
264 |
265 | **第一步:获取过滤器链**
266 |
267 | 如果存在,则 **TurboFilter** 过滤器会被调用,Turbo 过滤器会设置一个上下文的阀值,或者根据每一条相关的日志请求信息,例如:**Marker**, **Level**, **Logger**, 消息,**Throwable ** 来过滤某些事件。如果过滤器链的响应是 *FilterReply.DENY*,那么这条日志请求将会被丢弃。如果是 *FilterReply.NEUTRAL*,则会继续执行下一步,例如:第二步。如果响应是 *FilterRerply.ACCEPT*,则会直接跳到第三步。
268 |
269 | **第二步:应用[基本选择规则](#方法打印以及基本选择规则)**
270 |
271 | 在这步,logback 会比较有效级别与日志请求的级别,如果日志请求被禁止,那么 logback 将会丢弃调这条日志请求,并不会再做进一步的处理,否则的话,则进行下一步的处理。
272 |
273 | **第三步:创建一个 LoggingEvent 对象 **
274 |
275 | 如果日志请求通过了之前的过滤器,logback 将会创建一个 ch.qos.logback.classic.LoggingEvent 对象,这个对象包含了日志请求所有相关的参数,请求的 logger,日志请求的级别,日志信息,与日志一同传递的异常信息,当前时间,当前线程,以及当前类的各种信息和 MDC。MDC 将会在后续章节进行讨论。
276 |
277 | **第四步:调用 appender**
278 |
279 | 在创建了 LoggingEvent 对象之后,logback 会调用所有可用 appender 的 doAppend() 方法。这些 appender 继承自 logger 上下文。
280 |
281 | 所有的 appender 都继承了 AppenderBase 这个抽象类,并实现了 doAppend() 这个方法,该方法是线程安全的。AppenderBase 的 doAppend() 也会调用附加到 appender 上的自定义过滤器。自定义过滤器能动态的动态的添加到 appender 上,在过滤器章节会详细讨论。
282 |
283 | **第五步:格式化输出**
284 |
285 | 被调用的 Appender 负责格式化 Logging Event。但是,有些 Appender 将格式化 Logging Event 的任务委托给一个 Layout。Layout 将 LoggingEvent 实例格式化为一个字符串并返回。但需要注意的是,某些 Appender(例如 SocketAppender) 并不会把 Logging Event 转化为一个字符串,而是进行序列化。因此,它们没有并且也不需要 Layout。
286 |
287 | **第六步:发送 LoggingEvent**
288 |
289 | 当日志事件被完全格式化之后将会通过每个 appender 发送到具体的目的地。
290 |
291 | 下图是 logback 执行步骤的 UML 图:
292 |
293 | 
294 |
295 | ### 性能
296 |
297 | 记录日志经常被提到的一个点是它的计算代价。这是一个合理的考虑,因为一个中等大小的应用都可以产生成千上万的日志。我们的大部分努力都花在了测量以及调整 logback 的性能。但是用户还是应该知道以下有关性能的问题。
298 |
299 | 1. **当日志记录被关闭时记录日志的性能**
300 |
301 | 通过设置 root logger 的日志级别为 Level.OFF 来完全关闭日志的打印。当日志完全关闭的时候,日志请求的成本为方法的调用以及整数的比较。在 3.2Ghz 奔腾D 的电脑上的耗时大约为 20 纳秒。
302 |
303 | 任何方法的调用都有参数构建这个隐含的成本在里面。例如下面这个例子:
304 |
305 | ```java
306 | x.debug("Entry number: " + i + "is " + entry[i]);
307 | ```
308 |
309 | 把整数 i、entry[i] 转变为字符串,并且连接在一起,而不管这条日志是否会被打印。
310 |
311 | 构建参数的成本取决于参数的大小,为了避免不必要的性能损耗,可以使用 SLF4J's 的参数化构建:
312 |
313 | ```java
314 | x.debug("Entry number: {} is {}", i, entry[i]);
315 | ```
316 |
317 | 这种形式不会有构建参数的成本在里面。与上一个例子做比较,这个的速度会更快。只有当日志信息传递给了附加的 appender 时才会被格式化,而且格式化日志信息的组件也是被优化过的。
318 |
319 | 2. **当日记记录被打开时是否记录日志的性能**
320 |
321 | 在 logback 中,不需要遍历 logger 的层次结构。logger 在创建的时候就知道自己的有效级别。如果父级 logger 的级别被更改,则会通知所有子级 logger 注意这个更改。因此,在基于有效级别的基础上,logger 能够准实时的做出决定是否接受或者拒绝日志请求,而不需要考虑它的祖先的级别。
322 |
323 | 3. **日记记录的实际情况(格式化输出到指定设备)**
324 |
325 | 这是指格式化日志输出以及发送指定的目的地所需要的成本。我们尽可能快的让 layout(格式化)以及 appender 执行。在本地机器上,将日志输出到文件大概耗费 9-12 微秒的时间。当把日志输出到数据库或者远程服务器上时会上升到几毫秒。
326 |
327 | 尽管 logback 功能丰富,但是它最重要的目标之一是处理速度,这是仅次于可靠性的要求。为了提高性能,一些 logback 的组件被重写了几次。
328 |
--------------------------------------------------------------------------------
/11第十一章:Joran.md:
--------------------------------------------------------------------------------
1 | Joran 代表寒冷的西北风,常常猛烈的吹在日列瓦湖上。位于西欧中部的日列瓦湖,表面上看起来比其它许多欧洲的湖泊都要小。但是它的平均深度有 153 米,异常的深。并且,它是西欧最大的淡水湖。
2 |
3 | 正如前几章所示,logback 基于 Joran,一个成熟的,灵活的并且强大的配置框架。logback 提供的许多的功能,只能基于 Joran 来实现。这章将专注于 Joran 的基本设计以及一些明显的特征。
4 |
5 | Joran 实际上是一个通用的配置系统,能够被独立用于日志记录。为了强调这一点,我们需要说明的是 logback-core 模块没有 logger 的概念。所以,本章的大多数示例与 logger,appender,layout 无关。
6 |
7 | 本章节中的示例可以在 *LOGBACK_HOME/logback-examples/src/main/java/chapters/onJoran/* 文件夹下被找到。
8 |
9 | 要安装 Joran,只需要[下载](https://logback.qos.ch/download.html),然后将 *logback-core-1.3.0-alpha4.jar* 放到类路径下。
10 |
11 | ## 历史回顾
12 |
13 | 反射是 Java 语言一个强大的特性,使得声明式的配置软件系统变成可能。例如,EJB 许多重要的属性都被配置在 *ejb.xml* 文件中。尽管 EJB 是用 Java 编写的,但是它们的许多属性都是通过 *ejb.xml* 来指定的。类似的,logback 也可以通过指定的 XML 格式的配置文件来进行设置。JDK 1.5 中的注解在 EJB 3.0 被大量使用用来替换之前 XML 文件中的许多指令。Joran 也会充分利用注解,但是使用的范围少的多。由于 logback 配置的动态特性 (相比 EJB),Joran 使用注解相当有限。
14 |
15 | 在 logback 它爹 log4j 中, `DOMConfigurator` 类是 log4j 1.2.x 以及以后的版本的一部分。也能够解析 XML 的配置文件。`DOMConfigurator` 的编写方式强迫开发人员在配置文件的结构每次发生改变时,需要重新调整代码。调整的代码需要重新编译并重新部署。同样重要的是,`DOMConfigurator` 的代码由循环组成,用于解析子元素,包含了许多 if/else 语句。这样的代码散发着冗余以及重复的味道。 [commons-digester](http://jakarta.apache.org/commons/digester/) 告诉我们可以通过模式匹配规则来解析 XML 文件。在解析的时候,解析器会应用匹配了指定模式的规则。规则类通常比较小,并且具有专业性。因此,理解与维护相对简单。
16 |
17 | 有了 `DOMConfigurator` 的经验,我们开始开发 `Joran`,一个在 logback 中使用的、强大的配置框架。Joran 受到了 commons-digester 项目很大的启发。但是,它使用了一个稍微不同术语。在 commons-digester 中,规则可以看做由模式和规则组成,如同 `Digester.addRule(String pattern, Rule rule)` 方法展示的一样。我们发现一个不必要的困惑是规则包含自身,但是不是递归,而是有不同的含义。在 Joran 中,规则由模式以及动作组成。当相应的模式被匹配时,会调用一个动作。模式与动作的这种关系是 Joran 的核心。值得注意的是,可以使用简单的模式来处理复杂的匹配,或者更确切的是说是使用精确匹配以及通配符匹配。
18 |
19 | ### SAX 还是 DOM ?
20 |
21 | 由于 SAX API 是基于事件的结构,所以基于 SAX 的工具不能很好的处理前向引用,也就是引用元素被定义晚于当前元素被处理。循环引用元素也有同样的问题。通常,DOM API 允许用户在所有的元素上进行搜索,并且可以向前跳转。
22 |
23 | 这种额外的灵活性导致我们在开始的时候选择 DOM API 作为 Joran 的解析器。在经过了一些实验之后,我们发现当解析规则通过模式以及动作表达时,在解析 DOM 树时处理相隔较远的元素没有意义。*Joran 只需要 XML 文档中连续且深度优先顺序的元素。*
24 |
25 | 而且,在发生错误时,SAX API 提供元素的位置信息可以让 Joran 去展示精确的行号与列号。位置信息在识别解析问题时非常方便。
26 |
27 | ### 非目标
28 |
29 | 考虑到它的高度动态特性,Joran API 没有打算去解析包含几千个元素的 XML 文档。
30 |
31 | ### 模式 (Pattern)
32 |
33 | Joran 的模式本质上就是一个字符串。有两种形式的模式:*exact* 与 *wildcard*。模式 "a/b" 可以用来匹配嵌套在 `` 元素中的 `` 元素。因为 *exact* 匹配的设置,"a/b" 模式不会匹配其它的元素。
34 |
35 | wildcard 可以用来进行后缀与前缀匹配。例如,"\*/a" 可以用来匹配任何以 "a" 结尾的后缀,也就是 XML 文档中任何 `` 元素,但是不包含任何嵌套在 `` 中的元素。"a/\*" 将会匹配任何 `` 开头的元素,即任何嵌套在 `` 中的元素。
36 |
37 | ### 动作
38 |
39 | 正如之前提到的,Joran 解析规则由相关联的模式组成。动作继承了 [`Action`](https://logback.qos.ch/xref/ch/qos/logback/core/joran/action/Action.html) 类,包含了如下的抽象方法。为了简单起见,其它的方法被隐藏了。
40 |
41 | ```java
42 | package ch.qos.logback.core.joran.action;
43 |
44 | import org.xml.sax.Attributes;
45 | import org.xml.sax.Locator;
46 | import ch.qos.logback.core.joran.spi.InterpretationContext;
47 |
48 | public abstract class Action extends ContextAwareBase {
49 | /**
50 | * 当解析器遇到一个元素匹配
51 | * {@link ch.qos.logback.core.joran.spi.Pattern Pattern}.
52 | */
53 | public abstract void begin(InterpretationContext ic, String name,
54 | Attributes attributes) throws ActionException;
55 |
56 | /**
57 | * 传递包含元素的 body (作为字符串) 参数
58 | */
59 | public void body(InterpretationContext ic, String body)
60 | throws ActionException {
61 | // NOP
62 | }
63 |
64 | /*
65 | * 当解析器遇到最后一个元素匹配
66 | * {@link ch.qos.logback.core.joran.spi.Pattern Pattern}.
67 | */
68 | public abstract void end(InterpretationContext ic, String name)
69 | throws ActionException;
70 | }
71 | ```
72 |
73 | 所以,每个动作必须实现 `begin()` 与 `end()` 方法。`body()` 方法的实现是可选的,因为 `Action` 提供了一个空的实现。
74 |
75 | ### 规则存储
76 |
77 | 如前面提到的,根据匹配模式调用动作是 Joran 的核心概念。规则跟模式与动作相关联。规则被存储在 [RuleStore](https://logback.qos.ch/xref/ch/qos/logback/core/joran/spi/RuleStore.html) 中。
78 |
79 | 根据之前提到的,Joran 建立在 SAX API 上。当 XML 文档被解析时,每个元素会生成对应 start、body、end 的事件。当 Joran 的配置器接收到这些事件时,它会根据*当前模式*去规则存储中查找对应的动作。例如,元素 *B* 的 start、body、end 事件的当前模式为 "A/B",内嵌在一个顶级元素 *A* 中。当 Joran 接收并处理 SAX 事件时,它会自动维护当前模式的数据结构。
80 |
81 | 当有几个规则匹配到当前模式时,精确匹配会比后缀匹配优先,后缀匹配会比前缀匹配优先。对于详细的实现细节,请查看 [SimpleRuleStore](https://logback.qos.ch/xref/ch/qos/logback/core/joran/spi/SimpleRuleStore.html) 类。
82 |
83 | ### 解析上下文
84 |
85 | 为了允许多个动作相互协作,在调用 begin 与 end 方法时会包含解析上下文,作为第一个参数传递。解析上下文包含对象栈,对象映射,错误列表以及 Joran 调用动作时的一个引用。请查看 [`InterpretationContext`](https://logback.qos.ch/xref/ch/qos/logback/core/joran/spi/InterpretationContext.html) 类中详细的字段列表。
86 |
87 | 动作可以通过对象栈获取,入栈,出栈操作,或者通过对象映射来放置、获取 key 来进行协作。动作可以在解析上下文的 `StatusManager` 上通过添加错误项来报告任何错误条件。
88 |
89 | ### Hello world
90 |
91 | 这个章节中的第一个例子将会展示使用 Joran 所需要最小条件。这个例子由一个名为 [`HelloWorldAction`](https://logback.qos.ch/xref/chapters/onJoran/helloWorld/HelloWorldAction.html) 的动作组成。在调用它的 `begin()` 方法时会在控制台打印 "Hello World"。配置文件由解析器负责解析。为了实现本章的目的,我们实现了一个非常的简单的配置器 [`SimpleConfigurator`](https://logback.qos.ch/xref/chapters/onJoran/SimpleConfigurator.html)。[`HelloWorld`](https://logback.qos.ch/xref/chapters/onJoran/helloWorld/HelloWorld.html) 应用会将下面这些结合在一起:
92 |
93 | - 创建一个规则与 `Context` 的映射
94 | - 创建一个与 *hello-world* 模式相关的解析规则,以及对应的 `HelloWorldAction` 实例
95 | - 创建一个 `SimpleConfigutator`,解析之前提到的规则映射。
96 | - 调用配置器的 `doConfigure` 方法,解析 XML 文件
97 | - 最后,将会收集上下文中的所有转态信息。如果有的话,将会打印
98 |
99 | *hello.xml* 包含一个 \ 元素,没有任何其它的内置元素。详细的内容请查看 *logback-examples/src/main/java/chapters/onJoran/helloWorld/* 文件夹中的内容。
100 |
101 | 通过 *hello.xml* 运行 HelloWorld 应用将会在控制台输出 "Hello World"。
102 |
103 | ```java
104 | java chapters.onJoran.helloWorld.HelloWorld src/main/java/chapters/onJoran/helloWorld/hello.xml
105 | ```
106 |
107 | 强烈推荐你在规则存储中添加新的规则,更改 XML 配置 (hello.xml),以及添加新的动作。
108 |
109 | ### 动作相互合作
110 |
111 | *logback-examples/src/main/java/joran/calculator/* 文件夹包含了几个动作,为了完成简单的计算,它们通过共同的对象栈相互合作。
112 |
113 | *calculator1.xml* 文件包含一个 `computation` 元素,内嵌了一个 `literal` 元素。如下:
114 |
115 | > Example: calculator1.xml
116 |
117 | ```xml
118 |
119 |
120 |
121 | ```
122 |
123 | 在应用 [`Calculator1`](https://logback.qos.ch/xref/chapters/onJoran/calculator/Calculator1.html) 中,我们声明了各种解析规则 (模式与动作) 基于 XML 文档的内容一起合作来计算一个结果。
124 |
125 | 通过 *calculator1.xml* 运行 `Calculator`:
126 |
127 | ```jav
128 | java chapters.onJoran.calculator.Calculator1 src/main/java/chapters/onJoran/calculator/calculator1.xml
129 | ```
130 |
131 | 将会输出:
132 |
133 | ```java
134 | The computation named [total] resulted in the value 3
135 | ```
136 |
137 | 解析 *calculator1.xml* 文档包含如下的步骤:
138 |
139 | - 开始事件对应的 \ 元素转换为当前模式 "/computation"。因为在 [`Calculator1`](https://logback.qos.ch/xref/chapters/onJoran/calculator/Calculator1.html) 中我们为 "/computation" 模式关联了一个 [`ComputationAction1`](https://logback.qos.ch/xref/chapters/onJoran/calculator/ComputationAction1.html) 实例。`ComputationAction1` 实例中的 `begin()` 方法将会被调用
140 | - 开始事件对应的 \ 元素转换为当前模式 "/computation/literal"。为 "/computation/literal" 关联了一个 [`LiteralAction`](https://logback.qos.ch/xref/chapters/onJoran/calculator/LiteralAction.html) 实例。`LiteralAction` 实例中的 `begin()` 方法将会被调用。
141 | - 同样的,结束事件对应的 \ 元素将会触发 `ComputationAction1` 实例中的 `end()` 方法的调用。
142 |
143 | 有意思的是动作相互合作的方式。`LiteralAction` 读取到一个字面值,并将其放到对象栈中,由 `InterpretationContext` 来维护。一旦完成,其它的动作可以获取该值或者对其进行更改。`ComputationAction1` 类的 `end()` 方法从栈中获取值,并打印。
144 |
145 | 下一个例子中, *calculator2.xml* 有点复杂,但是更加有趣。
146 |
147 | > 有趣个鸡儿
148 |
149 | > Example:*calculator2.xml*
150 |
151 | ```xml
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | ```
160 |
161 | 在之前的例子中,为了响应 \ 元素,[`LiteralAction`](https://logback.qos.ch/xref/chapters/onJoran/calculator/LiteralAction.html) 实例会将 value 属性对应的整数放到解析上下文中的栈顶。在这个例子中,也就是 *calculator2.xml*,这个值是 7 与 3。为了响应 \ 元素。[`AddAction`](https://logback.qos.ch/xref/chapters/onJoran/calculator/AddAction.html) 实例将会获取之前放进去的两个整数,计算它们的和,然后再放进去。如,在解析上下文栈顶的就是 10 (= 7 + 3)。下个 literal 元素将会让 LiteralAction 将会将整数 3 放入栈顶。为了响应 \ 元素,[`MultiplyAction`](https://logback.qos.ch/xref/chapters/onJoran/calculator/MultiplyAction.html) 将会获取之前放入的两个整数 10 与 3,然后计算它们的乘积。它会将结果 30 放入栈顶。最后,为了响应结束事件对应的 \ 标签,ComputationAction1 将会打印堆栈顶部的结果,因此,运行:
162 |
163 | ```java
164 | java chapters.onJoran.calculator.Calculator1 src/main/java/chapters/onJoran/calculator/calculator2.xml
165 | ```
166 |
167 | 将会输出:
168 |
169 | ```java
170 | The computation named [toto] resulted in the value 30
171 | ```
172 |
173 | ### 默认动作
174 |
175 | 目前定义的队则都是被显示的动作调用。因为当前元素相关的模式/动作能够在规则存储中被找到。但是,在高度可以扩展的系统中,组件的数量跟类型都会非常多,因此对所有的模式都关联一个具体的动作将会变得十分的蛋疼。
176 |
177 | 同时,甚至在高扩展的系统中,可以看到重复的规则关联了不同的部分。如果我们可以识别这些规则,那么我们就可以在编译时处理由子组件组成的未知组件。例如,Apache Ant 有能力在编译时处理包含未知标签的任务,仅仅通过检查组件中的方法名是不是以 *add* 开头,像 `addFile` 或者 `addClassPath` 之类的。当 Ant 在任务内遇到一个内置的标签,它仅仅实例化一个匹配了任务类 add 方法的签名的对象,并且将结果对象附加到父级上。
178 |
179 | Joran 通过默认动作的形式来提供类似的功能。Joran 保留了一系列的默认动作,如果当前模式没有具体的模式可以匹配时,它们将会被应用。但是,应用默认的动作可能并不总是合适的。在执行默认的动作之前,Joran 会询问指定的动作当前的情况是否合适。只有动作返回肯定的回答,Joran 的配置器才会调用默认的动作。注意,这个额外的步骤可能支持多个默认的动作,如果在给定的情况下,没有合适的默认动作,也可能一个都不支持。
180 |
181 | 你可以创建并注册一个自定义的默认动作。见下一个示例。该示例位于 *logback-examples/src/main/java/chapters/onJoran/implicit* 文件夹下。
182 |
183 | [`PrintMe`](https://logback.qos.ch/xref/chapters/onJoran/implicit/PrintMe.html) 应用将一个 [`NOPAction`](https://logback.qos.ch/xref/chapters/onJoran/implicit/NOPAction.html) 实例与 "\*/foo" 模式相关联,也就是与名字叫做 "foo" 的任何元素。正如它的名字所示, `NOPAction` 的 `begin()` 与 `end()` 方法都为空。`PrintMe` 应用仍然会在它的默认动作列表注册一个 [PrintMeImplicitAction](https://logback.qos.ch/xref/chapters/onJoran/implicit/PrintMeImplicitAction.html) 的实例。`PrintMeImplicitAction` 对任何 *printme* 属性为 true 的元素有效。参见 `PrintMeImplicitAction` 的 `isApplicable()` 方法。`PrintMeImplicitAction` 的 `begin()` 方法会在控制台打印当前元素的名字。
184 |
185 | *implicit1.xml* XML 文档说明了默认动作是如何起作用的。
186 |
187 | > Example: *implicit1.xml*
188 |
189 | ```xml
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | ```
201 |
202 | 运行:
203 |
204 | ```java
205 | java chapters.onJoran.implicit.PrintMe src/main/java/chapters/onJoran/implicit/implicit1.xml
206 | ```
207 |
208 | 输出:
209 |
210 | ```java
211 | Element [xyz] asked to be printed.
212 | Element [abc] asked to be printed.
213 | 20:33:43,750 |-ERROR in c.q.l.c.joran.spi.Interpreter@10:9 - no applicable action for [xyz], current pattern is [[foo][xyz]]
214 | ```
215 |
216 | 给定一个 `NOPAction` 实例与 "*\/foo*" 实例相关联,`NOPAction` 的 `begin()` 与 `end()` 方法在 \ 元素上被调用。`PrintMeImplicitAction` 不会在任何 \ 元素上触发。对于其它的元素,因为没有明确的动作可以匹配,所以 `PrintMeImplicitAction` 的 `isApplicable()` 方法被调用。它只有在 *printme* 属性设置为 true 的时候才会返回 true。也就是第一个 \ 元素 (不是第二个) 与 \ 元素。第十行的第二个 \ 元素,没有可用的动作,所以生成了一个内部的错误信息。这个信息通过 `PrintMe` 的最后一行代码 `StatusPrinter.print` 来进行输出。这也解释了上面的输出。
217 |
218 | ### 在实践中使用默认动作
219 |
220 | logback-classic 与 logback-access 各自的 Joran 配置器只包含两个默认的动作,叫做 [`NestedBasicPropertyIA`](https://logback.qos.ch/xref/ch/qos/logback/core/joran/action/NestedBasicPropertyIA.html) 与 [`NestedComplexPropertyIA`](https://logback.qos.ch/xref/ch/qos/logback/core/joran/action/NestedComplexPropertyIA.html)。
221 |
222 | `NestedBasicPropertyIA` 适用于任何属性的类型为原始类型 (或者 equivalent object type in the `java.lang` 包中的对象类型 ),枚举类,或者其它遵循 "valuesOf" 约定的类型。这些属性被称之为*基本* 或者*简单* 属性。如果一个类它包含一个名为 `valueOf()` 的静态方法,接受一个 `java.lang.String` 作为参数并且返回相关类型的实例,那么就说这个类遵循 "valueOf" 约定。目前,[`Level`](https://logback.qos.ch/xref/ch/qos/logback/classic/Level.html),[`Duration`](https://logback.qos.ch/xref/ch/qos/logback/core/util/Duration.html),以及 [`FileSize`](https://logback.qos.ch/xref/ch/qos/logback/core/util/FileSize.html) 类遵循这个约定。
223 |
224 | `NestedComplexPropertyIA` 适用于 `NestedBasicPropertyIA` 不适用的情况,并且如果对象栈顶部的对象具有当前属性名的 set 与 add 方法,那么该属性名相当于当前元素的名称。这些属性可以反过来包含其它的组件。因此,这些属性可以说是有点*复杂*。由于复杂属性的存在,[`NestedComplexPropertyIA`](https://logback.qos.ch/xref/ch/qos/logback/core/joran/action/NestedComplexPropertyIA.html) 将会为内部组件实例化一个合适的类,并且通过使用父组件以及内部元素名的 set/add 方法将其附加到父组件上 (在对象栈的顶部)。相应的类通过当前 (内置) 元素的 *class* 属性来指定。但是,如果没有指定 *class* 属性,那么将会根据是否满足以下其中之一的条件来使用默认的类名。
225 |
226 | 1. 父对象属性指定的类有内部的规则相关联
227 | 2. set 方法包含一个指定类的 @DefaultClass 属性
228 | 3. set 方法的参数类型是一个含有共有构造方法的实体类
229 |
230 | #### 默认类映射
231 |
232 | 在 logback-classic,有一些内部的规则将父 类/属性 名映射为默认的类。这些规则如下表所示:
233 |
234 | | 父类 | 属性名 | 默认类 |
235 | | ---------------------------------------------- | --------- | --------------------------------------------------- |
236 | | ch.qos.logback.core.AppenderBase | encoder | ch.qos.logback.classic.encoder.PatternLayoutEncoder |
237 | | ch.qos.logback.core.UnsynchronizedAppenderBase | encoder | ch.qos.logback.classic.encoder.PatternLayoutEncoder |
238 | | ch.qos.logback.core.AppenderBase | layout | ch.qos.logback.classic.PatternLayout |
239 | | ch.qos.logback.core.UnsynchronizedAppenderBase | layout | ch.qos.logback.classic.PatternLayout |
240 | | ch.qos.logback.core.filter.EvaluatorFilter | evaluator | ch.qos.logback.classic.boolex.JaninoEventEvaluator |
241 |
242 | 在以后的版本中,这个列表可能会发生改变。最新的规则,请查看 logback-classic 中 [JoranConfigurator](https://logback.qos.ch/xref/ch/qos/logback/classic/joran/JoranConfigurator.html) 中的 `addDefaultNestedComponentRegistryRules` 方法。
243 |
244 | #### 属性集
245 |
246 | 除了单个简单属性以及单个复杂属性外,logback 的默认动作支持属性集,它们可以是简单的或者复杂的。指定的属性通过 "add" 方法,而不是 set 方法。
247 |
248 | ### 动态添加新规则
249 |
250 | Joran 包含一个允许 Joran 在解析 XML 文档的过程中,动态解析新规则的动作。示例代码,查看 *logback-examples/src/main/java/chapters/onJoran/newRule/* 文件夹。在这个包中,[`NewRuleCalculator`](https://logback.qos.ch/xref/chapters/onJoran/newRule/NewRuleCalculator.html) 仅仅设置两个规则,一个用来处理最顶层的元素,一个用来学习新规则。下面是 `NewRuleCalculator` 中相关的代码:
251 |
252 | ```java
253 | ruleMap.put(new Pattern("*/computation"), new ComputationAction1());
254 | ruleStore.addRule(new Pattern("/computation/newRule"), new NewRuleAction());
255 | ```
256 |
257 | [`NewRuleAction`](https://logback.qos.ch/xref/ch/qos/logback/core/joran/action/NewRuleAction.html) 是 logback-core 的一部分,跟其它的动作非常的类似。它有 `begin()` 与 `end()` 方法,在每次解析器找到一个 *newRule* 元素时被调用。当被调用时,`begin()` 方法会去寻找 *pattern* 与 *actionClass* 属性。然后实例化相应的动作类,并将模式/动作作为一条新规则添加到 Joran 的规则存储中。
258 |
259 | 下面是如何在 xml 文件添加一条新规则:
260 |
261 | ```xml
262 |
264 | ```
265 |
266 | 使用 newRule 声明,我们可以看到 `NewRuleCalculator` 表现出跟之前看到的 `Calculator1` 同样的结果。包括计算在内,我们可以按照如下的方式进行表示:
267 |
268 | > Example: *newRule.xml*
269 |
270 | ```xml
271 |
272 |
274 |
276 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 | ```
289 |
290 | ```java
291 | java chapters.onJoran.newRule.NewRuleCalculator src/main/java/chapters/onJoran/newRule/newRule.xml
292 | ```
293 |
294 | > 作者原文中的命令写了两个 java
295 |
296 | 输出:
297 |
298 | ```java
299 | The computation named [toto] resulted in the value 30
300 | ```
301 |
302 | 跟[之前](https://github.com/Volong/logback-chinese-manual/blob/master/11%E7%AC%AC%E5%8D%81%E4%B8%80%E7%AB%A0%EF%BC%9AJoran.md#%E5%8A%A8%E4%BD%9C%E7%9B%B8%E4%BA%92%E5%90%88%E4%BD%9C)的结果一致。
303 |
304 | > 运行原文中的示例并不会输出坐着所说的结果,需要去掉 \ 中的 \ 标签才可以
--------------------------------------------------------------------------------
/08第八章:MDC.md:
--------------------------------------------------------------------------------
1 | logback 设计的目标之一是审计与调试复杂的分布式应用。大部分的分布式系统需要同时处理多个客户端。在一个系统典型的多线程实现中,不同的线程处理不同的客户端。一种可能但是不建议的方式是在每个客户端实例化一个新的且独立的 logger,来区分一个客户端与另一个客户端的日志输出。这种方式会导致 logger 急剧增加并且会增加维护成本。
2 |
3 | 一种轻量级的技术是给每个为客户端服务的 logger 打一个标记。Neil Harrison 在 *Patterns for Logging Diagnostic Messages* in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 这本书中描述了这种方法。logback 在 SLF4J API 利用了这种技术的变体:诊断上下文映射 (MDC)。
4 |
5 | 为了给每个请求打上唯一的标记,用户需要将上下文信息放到 `MDC` (Mapped Diagnostic Context 的缩写) 中。下面列出了 MDC 类中主要的部分。完成的方法列表请查看 [MDC javadocs](http://www.slf4j.org/api/org/slf4j/MDC.html)。
6 |
7 | ```java
8 | package org.slf4j;
9 |
10 | public class MDC {
11 | // 将上下文的值作为 MDC 的 key 放到线程上下的 map 中
12 | public static void put(String key, String val);
13 |
14 | // 通过 key 获取上下文标识
15 | public static String get(String key);
16 |
17 | // 通过 key 移除上下文标识
18 | public static void remove(String key);
19 |
20 | // 清除 MDC 中所有的 entry
21 | public static void clear();
22 | }
23 | ```
24 |
25 | `MDC` 类中只包含静态方法。它让开发人员可以在 *诊断上下文* 中放置信息,而后通过特定的 logback 组件去获取。`MDC` 在 *每个线程的基础上* 管理上下文信息。通常,当为一个新客户端启动服务时,开发人员会将特定的上文信息插入到 MDC 中。例如,客户端 id,客户端 IP 地址,请求参数等。如果 logback 组件配置得当的话,会自动在每个日志条目中包含这些信息。
26 |
27 | 请注意,logback-classic 实现的 MDC,假设值以适当的频率放置。还需注意的一点是,子线程不会自动继承父线程的 MDC。
28 |
29 | 下面的 [SimpleMDC](https://logback.qos.ch/xref/chapters/mdc/SimpleMDC.html) 说明了这点。
30 |
31 | > Example: *[SimpleMDC.java](https://logback.qos.ch/xref/chapters/mdc/SimpleMDC.html)*
32 |
33 | ```java
34 | package chapters.mdc;
35 |
36 | import org.slf4j.Logger;
37 | import org.slf4j.LoggerFactory;
38 | import org.slf4j.MDC;
39 |
40 | import ch.qos.logback.classic.PatternLayout;
41 | import ch.qos.logback.core.ConsoleAppender;
42 |
43 | public class SimpleMDC {
44 | static public void main(String[] args) throws Exception {
45 |
46 | // 你可以选择在任何时候将值放入 MDC 中
47 | MDC.put("first", "Dorothy");
48 |
49 | [ SNIP ]
50 |
51 | Logger logger = LoggerFactory.getLogger(SimpleMDC.class);
52 |
53 | MDC.put("last", "Parker");
54 |
55 | logger.info("Check enclosed.");
56 | logger.debug("The most beautiful two words in English.");
57 |
58 | MDC.put("first", "Richard");
59 | MDC.put("last", "Nixon");
60 | logger.info("I am not a crook.");
61 | logger.info("Attributed to the former US president. 17 Nov 1973.");
62 | }
63 |
64 | [ SNIP ]
65 |
66 | }
67 | ```
68 |
69 | main 方法在启动的时候在 `MDC` 中将 *Dorothy* 关联到 *first* 上。你可以在 `MDC` 放置尽可能多的键值对,反正你开心就好。多次插入同一个 key,新值会覆盖旧值。然后代码继续配置 logback。
70 |
71 | 为了简洁,我们隐藏了配置文件 [simpleMDC.xml](http://github.com/qos-ch/logback/blob/master/logback-examples/src/main/java/chapters/mdc/simpleMDC.xml) 中的其它配置。下面的一些相关的配置。
72 |
73 | ```xml
74 |
75 |
76 | %X{first} %X{last} - %m%n
77 |
78 |
79 | ```
80 |
81 | 注意 *X%* 转换符在 `PatternLayout` 中的用法。*X%* 转换符使用了两次,一次用于 key 为 *first*,一次用于 key 为 *last*。在获得了 `SimpleMDC.class` 中的 logger 之后,通过代码将 *Parker* 关联到 *last* 上。然后通过 logger 打印了两条不同的信息。最后,通过代码重新设置了 `MDC` 为不同的值,并打印了几条日志请求。运行 SimpleMDC 将会输出:
82 |
83 | ```java
84 | Dorothy Parker - Check enclosed.
85 | Dorothy Parker - The most beautiful two words in English.
86 | Richard Nixon - I am not a crook.
87 | Richard Nixon - Attributed to the former US president. 17 Nov 1973.
88 | ```
89 |
90 | `SimpleMDC` 展示了正确配置 logback layout 就可以自动输出 `MDC` 的信息。而且,`MDC` 中信息可以被多个 logger 使用。
91 |
92 | ### 高级用法
93 |
94 | MDC 在客户端服务器架构中最为重要。通常,服务器上的多个线程为多个客户端提供服务。尽管 MDC 中的方法是静态的,但是是以每个线程为基础来进行管理的,允许每个服务线程都打上一个 `MDC` 标记。`MDC` 中的 `put()` 与 `get()` 操作仅仅只影响当前线程中的 `MDC`。其它线程中 `MDC` 不会受到影响。给定的 `MDC` 信息在每个线程的基础上进行管理。每个线程都有一份 `MDC` 的拷贝。因此,在对 `MDC` 进行编程时,开发人员没有必要去考虑线程安全或者同步问题。它自己会安全的处理这些问题。
95 |
96 | 下面有一个稍微高级一点的例子。它显示了如何在客户端-服务端上设置 `MDC`。例 7.2 展示的是服务端实现 `NumberCruncher` 接口。`NumberCruncher` 接口仅仅只包含一个方法 `factor()`。客户端使用 RMI 技术,调用服务器应用程序上的 `factor()` 方法来获取一个整数的不同因子。
97 |
98 | > Example 7.2 *NumberCruncher.java*
99 |
100 | ```java
101 | package chapters.mdc;
102 |
103 | import java.rmi.Remote;
104 | import java.rmi.RemoteException;
105 |
106 | public interface NumberCruncher extends Remote {
107 | int[] factor(int number) throws RemoteException;
108 | }
109 | ```
110 |
111 | 例 7.3 展示了 `NumberCruncherServer` 实现了 `NumberCruncher` 接口。它的主要方法是在本地主机上导出一个 RMI 注册表,并在一个众所周知的端口号上接受请求 (反正我是不知道😓)。
112 |
113 | > *Example 7.3* *NumberCruncherServer.java*
114 |
115 | ```java
116 | package chapters.mdc;
117 |
118 | import java.rmi.RemoteException;
119 | import java.rmi.registry.LocateRegistry;
120 | import java.rmi.registry.Registry;
121 | import java.rmi.server.UnicastRemoteObject;
122 | import java.util.Vector;
123 |
124 | import org.slf4j.Logger;
125 | import org.slf4j.LoggerFactory;
126 | import org.slf4j.MDC;
127 |
128 | import ch.qos.logback.classic.LoggerContext;
129 | import ch.qos.logback.classic.joran.JoranConfigurator;
130 | import ch.qos.logback.core.joran.spi.JoranException;
131 |
132 |
133 | /**
134 | * A simple NumberCruncher implementation that logs its progress when
135 | * factoring numbers. The purpose of the whole exercise is to show the
136 | * use of mapped diagnostic contexts in order to distinguish the log
137 | * output from different client requests.
138 | * */
139 | public class NumberCruncherServer extends UnicastRemoteObject
140 | implements NumberCruncher {
141 |
142 | private static final long serialVersionUID = 1L;
143 |
144 | static Logger logger = LoggerFactory.getLogger(NumberCruncherServer.class);
145 |
146 | public NumberCruncherServer() throws RemoteException {
147 | }
148 |
149 | public int[] factor(int number) throws RemoteException {
150 | // The client's host is an important source of information.
151 | try {
152 | MDC.put("client", NumberCruncherServer.getClientHost());
153 | } catch (java.rmi.server.ServerNotActiveException e) {
154 | logger.warn("Caught unexpected ServerNotActiveException.", e);
155 | }
156 |
157 | // The information contained within the request is another source
158 | // of distinctive information. It might reveal the users name,
159 | // date of request, request ID etc. In servlet type environments,
160 | // useful information is contained in the HttpRequest or in the
161 | // HttpSession.
162 | MDC.put("number", String.valueOf(number));
163 |
164 | logger.info("Beginning to factor.");
165 |
166 | if (number <= 0) {
167 | throw new IllegalArgumentException(number +
168 | " is not a positive integer.");
169 | } else if (number == 1) {
170 | return new int[] { 1 };
171 | }
172 |
173 | Vector factors = new Vector();
174 | int n = number;
175 |
176 | for (int i = 2; (i <= n) && ((i * i) <= number); i++) {
177 | // It is bad practice to place log requests within tight loops.
178 | // It is done here to show interleaved log output from
179 | // different requests.
180 | logger.debug("Trying " + i + " as a factor.");
181 |
182 | if ((n % i) == 0) {
183 | logger.info("Found factor " + i);
184 | factors.addElement(new Integer(i));
185 |
186 | do {
187 | n /= i;
188 | } while ((n % i) == 0);
189 | }
190 |
191 | // Placing artificial delays in tight loops will also lead to
192 | // sub-optimal results. :-)
193 | delay(100);
194 | }
195 |
196 | if (n != 1) {
197 | logger.info("Found factor " + n);
198 | factors.addElement(new Integer(n));
199 | }
200 |
201 | int len = factors.size();
202 |
203 | int[] result = new int[len];
204 |
205 | for (int i = 0; i < len; i++) {
206 | result[i] = ((Integer) factors.elementAt(i)).intValue();
207 | }
208 |
209 | // clean up
210 | MDC.remove("client");
211 | MDC.remove("number");
212 |
213 | return result;
214 | }
215 |
216 | static void usage(String msg) {
217 | System.err.println(msg);
218 | System.err.println("Usage: java chapters.mdc.NumberCruncherServer configFile\n" +
219 | " where configFile is a logback configuration file.");
220 | System.exit(1);
221 | }
222 |
223 | public static void delay(int millis) {
224 | try {
225 | Thread.sleep(millis);
226 | } catch (InterruptedException e) {
227 | }
228 | }
229 |
230 | public static void main(String[] args) {
231 | if (args.length != 1) {
232 | usage("Wrong number of arguments.");
233 | }
234 |
235 | String configFile = args[0];
236 |
237 | if (configFile.endsWith(".xml")) {
238 | try {
239 | LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
240 | JoranConfigurator configurator = new JoranConfigurator();
241 | configurator.setContext(lc);
242 | lc.reset();
243 | configurator.doConfigure(args[0]);
244 | } catch (JoranException je) {
245 | je.printStackTrace();
246 | }
247 | }
248 |
249 | NumberCruncherServer ncs;
250 |
251 | try {
252 | ncs = new NumberCruncherServer();
253 | logger.info("Creating registry.");
254 |
255 | Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
256 | registry.rebind("Factor", ncs);
257 | logger.info("NumberCruncherServer bound and ready.");
258 | } catch (Exception e) {
259 | logger.error("Could not bind NumberCruncherServer.", e);
260 |
261 | return;
262 | }
263 | }
264 | }
265 | ```
266 |
267 | `factor(int number)` 的实现特别的相关。它在启动的时候将客户端的名字通过 key *client* 放到 `MDC` 中。客户端通过请求将整数放到 `MDC` 中的 *number* 这个 key 下。在计算出整数的不同因子之后,然后将结果返回给客户端。在返回结果之前,通过调用 `MDC.remove()` 清除 *client* 与 *number* 的值。通常,`put()` 操作应该由 `remove()` 操作来平衡。否则的话,`MDC` 将会保留旧值。我们推荐无论何时,在 finally 代码块中调用 `remove` 操作,用来确保该操作的执行,而不用关心代码的具体执行路径。
268 |
269 | 在解释完理论之后,我们准备运行这个例子。通过一下命令运行这个服务:
270 |
271 | ```bash
272 | java chapters.mdc.NumberCruncherServer src/main/java/chapters/mdc/mdc1.xml
273 | ```
274 |
275 | *mdc1.xml* 的内容如下:
276 |
277 | > Example: *mdc1.xml*
278 |
279 | ```xml
280 |
281 |
282 |
283 | %-4r [%thread] %-5level C:%X{client} N:%X{number} - %msg%n
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 | ```
292 |
293 | 注意 *%X* 转换符在 `Pattern` 选项中使用。
294 |
295 | 通过以下命令执行 `NumberCruncherClient` 应用:
296 |
297 | ```bash
298 | java chapters.mdc.NumberCruncherClient hostname
299 | ```
300 |
301 | *hostname* 是 `NumberCruncherServer` 运行的主机名。
302 |
303 | 在客户端执行多个实例,第一个客户端请求服务器对 129 进行因数分解,随后,第二个客户端请求服务器对 71 进行因数分解。服务器的输出如下:
304 |
305 | ```java
306 | 70984 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Beginning to factor.
307 | 70984 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 2 as a factor.
308 | 71093 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 3 as a factor.
309 | 71093 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Found factor 3
310 | 71187 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 4 as a factor.
311 | 71297 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 5 as a factor.
312 | 71390 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 6 as a factor.
313 | 71453 [RMI TCP Connection(5)-192.168.1.6] INFO C:orion N:71 - Beginning to factor.
314 | 71453 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 2 as a factor.
315 | 71484 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 7 as a factor.
316 | 71547 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 3 as a factor.
317 | 71593 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 8 as a factor.
318 | 71656 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 4 as a factor.
319 | 71687 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 9 as a factor.
320 | 71750 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 5 as a factor.
321 | 71797 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 10 as a factor.
322 | 71859 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 6 as a factor.
323 | 71890 [RMI TCP Connection(4)-192.168.1.6] DEBUG C:orion N:129 - Trying 11 as a factor.
324 | 71953 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 7 as a factor.
325 | 72000 [RMI TCP Connection(4)-192.168.1.6] INFO C:orion N:129 - Found factor 43
326 | 72062 [RMI TCP Connection(5)-192.168.1.6] DEBUG C:orion N:71 - Trying 8 as a factor.
327 | 72156 [RMI TCP Connection(5)-192.168.1.6] INFO C:orion N:71 - Found factor 71
328 | ```
329 |
330 | 从上面的输出可以看出客户端运行在一个叫做 *orion* 的主机上。尽管服务器在独立的线程中几乎同时处理客户端的请求,但是可以通过 `MDC` 来区分每个客户端的日志输出。示例中的是通过 *number* 来进行标识。
331 |
332 | 细心的读者可能已经注意到,可以通过线程名来区分每个请求。如果服务端重复使用线程,那么使用线程名可能会导致一下误解。也就是一个给定线程在服务完请求之后,接着去服务下一个请求,在这种情况下,很难去区分每个请求。因为 `MDC` 是受应用开发人员的控制,所以不会受这个影响。(是吗?开发人员就这么可靠吗?)
333 |
334 | ### 自动获取 `MDC`
335 |
336 | 正如我们之前看到的,对多个客户端进行请求时,`MDC` 非常有用。在 web 应用中管理用户认证,一个简单的处理方式是在 `MDC` 中设置用户名,在用户退出时进行移除。不幸的是,使用这个技术并不总是可靠的。因为 `MDC` 是在单线程的基础进行管理,服务器重复使用线程可能会导致 `MDC` 包含错误的信息。
337 |
338 | 为了在处理请求时,`MDC` 中包含的信息一直都是正确的。一种可能的处理方式是在开始处理之前存储用户名,结束时进行移除。[`Filter`](http://java.sun.com/javaee/5/docs/api/javax/servlet/Filter.html) 可以用来处理这种情况。
339 |
340 | 在 servlet 过滤器 `doFilter` 方法中,我们可以通过 request (或者 cookie) 获取用户的相关信息,并存到 `MDC` 中。其它过滤器或者 servlet 后续的处理会从 `MDC` 中受益。最后,当我们的 servlet 过滤器再次获得控制时,就有机会去清除 MDC 中的数据。
341 |
342 | 下面是一个过滤器的实现:
343 |
344 | > Example: *UserServletFilter.java*
345 |
346 | ```java
347 | package chapters.mdc;
348 |
349 | import java.io.IOException;
350 | import java.security.Principal;
351 |
352 | import javax.servlet.Filter;
353 | import javax.servlet.FilterChain;
354 | import javax.servlet.FilterConfig;
355 | import javax.servlet.ServletException;
356 | import javax.servlet.ServletRequest;
357 | import javax.servlet.ServletResponse;
358 | import javax.servlet.http.HttpServletRequest;
359 | import javax.servlet.http.HttpSession;
360 |
361 | import org.slf4j.MDC;
362 |
363 | public class UserServletFilter implements Filter {
364 |
365 | private final String USER_KEY = "username";
366 |
367 | public void destroy() {
368 | }
369 |
370 | public void doFilter(ServletRequest request, ServletResponse response,
371 | FilterChain chain) throws IOException, ServletException {
372 |
373 | boolean successfulRegistration = false;
374 |
375 | HttpServletRequest req = (HttpServletRequest) request;
376 | Principal principal = req.getUserPrincipal();
377 | // Please note that we could have also used a cookie to
378 | // retrieve the user name
379 |
380 | if (principal != null) {
381 | String username = principal.getName();
382 | successfulRegistration = registerUsername(username);
383 | }
384 |
385 | try {
386 | chain.doFilter(request, response);
387 | } finally {
388 | if (successfulRegistration) {
389 | MDC.remove(USER_KEY);
390 | }
391 | }
392 | }
393 |
394 | public void init(FilterConfig arg0) throws ServletException {
395 | }
396 |
397 |
398 | /**
399 | * Register the user in the MDC under USER_KEY.
400 | *
401 | * @param username
402 | * @return true id the user can be successfully registered
403 | */
404 | private boolean registerUsername(String username) {
405 | if (username != null && username.trim().length() > 0) {
406 | MDC.put(USER_KEY, username);
407 | return true;
408 | }
409 | return false;
410 | }
411 | }
412 | ```
413 |
414 | 当过滤器的 `doFilter` 被调用时,它首先会在 request 中去寻找 `java.security.Principal` 对象。这个对象包含了当前认证用户的名字。如果找到了用户的信息,会将它注册到 `MDC` 中。
415 |
416 | 一个过滤器链执行完成,那么这个过滤器会从 `MDC` 中移除用户信息。
417 |
418 | 刚才我们描述的方法只是在请求期间,或者线程处理中才去设置 MDC 的值。其它的线程不会受到影响。而且,任何给定的线程在任何时候都会包含正确的 MDC 数据。
419 |
420 | ### MDC 与线程管理
421 |
422 | MDC 的副本不能总是由工作线程从初始化线程继承。当使用 `java.util.concurrent.Executors` 进行线程管理时就是这种情况。例如,`newCachedThreadPool` 方法会创建一个 `ThreadPoolExecutor`,跟其它线程池代码一样,有着复杂的线程创建逻辑。
423 |
424 | 在这些情况下,推荐在提交任务去执行之前,在源线程上调用 `MDC.getCopyOfContextMap()`。当任务运行的时候,它的第一个动作,应该是调用 `MDC.setContextMapValues()` 将原始 MDC 值的存储副本与新的 `Executor` 管理线程关联起来。
425 |
426 | ### MDCInsertingServletFilter
427 |
428 | 在 web 应用中,获取给定 HTTP 请求的主机名,请求 URI 以及 user-agent 是十分有用的。[`MDCInsertingServletFilter`](https://logback.qos.ch/xref/ch/qos/logback/classic/helpers/MDCInsertingServletFilter.html) 通过如下的 key,将数据插入到 MDC 中。
429 |
430 | | MDC key | MDC value |
431 | | ------------------- | ------------------------------------------------------------ |
432 | | `req.remoteHost` | 由 [getRemoteHost()](http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/ServletRequest.html#getRemoteHost%28%29) 返回 |
433 | | `req.xForwardedFor` | 请求头 ["X-Forwarded-For"](http://en.wikipedia.org/wiki/X-Forwarded-For) 的值 |
434 | | `req.method` | 由 [getMethod()](http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getMethod%28%29) 返回 |
435 | | `req.requestURI` | 由 [getRequestURI()](http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getRequestURI%28%29) 返回 |
436 | | `req.requestURL` | 由 [getRequestURL()](http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getRequestURL%28%29) 返回 |
437 | | `req.queryString` | 由 [getQueryString()](http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html#getQueryString%28%29) 返回 |
438 | | `req.userAgent` | 请求头 "User-Agent" 的值 |
439 |
440 | 想要使用 `MDCInsertingServletFilter`,需要在 *web.xml* 中添加以下行:
441 |
442 | ```xml
443 |
444 | MDCInsertingServletFilter
445 |
446 | ch.qos.logback.classic.helpers.MDCInsertingServletFilter
447 |
448 |
449 |
450 | MDCInsertingServletFilter
451 | /*
452 |
453 | ```
454 |
455 | **如果你的 web 应用有多个过滤器,请确保 `MDCInsertingServletFilter` 在其它过滤器之前声明 **。例如,假设 web 应用的主要处理是在过滤器 'F' 中完成,那么如果 `MDCInsertingServletFilter` 在 'F' 之后被调用,那么在 'F' 中被调用的代码将看不到 `MDCInsertingServletFilter` 设置的 MDC 的值。(这不是废话吗......)
456 |
457 | 一旦使用了该过滤器,通过 %X 可以输出相对应的 MDC 的值。例如,想要在一行上打印远程主机,后面跟着请求的 URI,令一行打印日志,后面跟着日志消息。那么你应该如下设置 `PatternLayout`:
458 |
459 | ```xml
460 | %X{req.remoteHost} %X{req.requestURI}%n%d - %m%n
461 | ```
462 |
463 |
464 |
465 |
--------------------------------------------------------------------------------
/15第十五章:使用 SSL.md:
--------------------------------------------------------------------------------
1 | 在从以 socket 为基础的 appender 到远程 receiver 传递日志事件时,logback 支持使用安全套接字层。当使用支持 SSL 的 appender 以及响应的 receiver 时,通过安全通道来传递日志事件。
2 |
3 | ## SSL 与组件的角色
4 |
5 | logback 的组件,例如 appender 以及 receiver 在网络连接初始化时可能承当服务器的角色或者客户端的角色。当充当服务器角色时,logback 组件被动的监听来自远程客户端组件的连接。相反地,充当客户端角色的 logback 组件会初始化一个连接到远程服务器组件。例如,一个充当客户端角色的 appender 连接充当服务端角色的 receiver。或者一个充当客户端角色的 receiver 连接充当服务端角色的 appender。
6 |
7 | 组件的角色通常由组件的类型决定。例如,`SSLServerSocketAppender` 是一个充当服务端角色的 appender 组件,但是 `SSLSocketAppender` 是一个充当客户端角色的 appender 组件。因此开发者或者应用管理人员可以配置 logback 组件来支持想要的网络连接初始化方向。
8 |
9 | 网络连接初始化方法在 SSL 上下文中非常重要,因为在 SSL 中,服务端组件连接客户端必须具有 X.509 证书来标识自身。客户端组件,在连接服务端时,使用服务端的证书来验证服务端是否可信。开发人员或者应用管理者必须清楚的知道 logback 组件的角色,才能正确配置服务器的 keystore (包含服务器 X.509 证书) 以及客户端的 truststore (包含用于验证服务器信任时的自签名根证书)。
10 |
11 | 当为 **相互认证** 配置 SSL,服务器组件与客户端组件必须拥有有效的 X.509 证书,它们的信任由它们各自对等的声明。相互认证配置在服务器组件中,因此开发人员或者应用管理者必须知道哪一个组件充当的是服务器的角色。
12 |
13 | 在本章,我们使用术语 *服务器组件* 或者简单的 *服务器* 来表示 logback 中充当服务器角色的 appender 或者 receiver。使用 *客户端组件* 或者建的 *客户端* 来表示充当客户端角色的组件。
14 |
15 | ## SSL 以及 X.509 证书
16 |
17 | 为了是用支持 SSL 的 logback 组件,你需要一个 X.509 的证书 (一个私钥,相应的证书,以及 CA 认证链) 去标识你的组件充当了一个 SSL 服务器。如果你想要使用双向认证,那么充当 SSL 客户端的组件还需要一个证书。
18 |
19 | 虽然你可以使用由商业认证机构 (CA) 颁发的证书,但是你也可以使用内部 CA 颁发的证书,甚至是自签名的证书。下面是必须满足的条件:
20 |
21 | 1. 服务端组件必须配置一个 key store,包含服务器私钥,相应的证书,以及 CA 认证链 (如果没有使用自签名证书)
22 | 2. 客户端组件必须配置一个 trust store,包含可信任的根 CA 证书,或者服务器自签名根证书
23 |
24 | ## 为 SSL 配置 logback 组件
25 |
26 | Java 安全套接字拓展 (JSSE) 以及 Java 加密体系 (JCA) 用来实现 logback 的 SSL,支持诸多的配置选项。可插拔的提供框架允许替换或增强内置的 SSL 以及平台的加密功能。为了满足你的对安全的需要,支持 SSL 的 logback 组件提供完全指定 SSL 引擎以及密码提供者配置方面的能力。
27 |
28 | ### 使用 JSSE 系统属性的基本 SSL 配置
29 |
30 | 幸运的是,支持 SSL 的 logback 组件几乎所有的 SSL 属性的配置都有合理的默认值。在多数的情况下,只需要配置一些 JSSE 的系统属性。
31 |
32 | 本节剩余的部分将讲述在大多数的环境中都需要被指定的 JSSE 属性。查看[定制 JSSE](https://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#InstallationAndCustomization)来获取更多关于设置 JSSE 系统属性的信息来定制 JSSE。
33 |
34 | 如果你使用任何 logback 中支持 SSL 的 appender 或者 receiver 组件充当服务端角色 (例如,`SSLServerSocketReceiver`,`SSLServerSocketAppender`,`SimpleSSLSocketServer`)。你需要提供 key store 包含的密钥以及证书的 location,type 以及 password 来配置 JSEE 系统属性。
35 |
36 | #### 服务端 key store 配置的系统属性
37 |
38 | | 属性名 | 描述 |
39 | | -------------------------------- | ------------------------------------------------------- |
40 | | `javax.net.ssl.keyStore` | 指定包含服务端组件的密钥以及证书的文件的文件系统的路径 |
41 | | `javax.net.ssl.keyStoreType` | 指定 key store 类型。如果这个属性没有指定,将默认为 JKS |
42 | | `javax.net.ssl.keyStorePassword` | 指定访问 key store 的密码 |
43 |
44 | 查看下面 [Examples]([Examples](https://logback.qos.ch/manual/usingSSL.html#Examples)) 部分,在应用启动的时候通过利用 logback 支持 SSL 的服务端组件来设置这些系统属性。
45 |
46 | 如果你的服务端组件使用的证书是商业认证机构 (CA) 认证,**你可能不需要在你应用的客户端组件中提供任何 SSL 配置。**当在你的服务端组件使用商业签名证书,仅仅只需要在运行服务端组件的 JVM 上设置系统的 key store 属性。
47 |
48 | 如果你使用自签名的服务器证书或者你服务器证书是认证机构 (CA) 签名但是它们的根证书不再 Java 平台默认的 trust store 中 (例如,你的组织有自己的内部签名机构)。你需要配置 JSSE 系统属性,以提供包含你服务器证书的 trust store 或者认证机构 (CA) 颁发的受信任根证书签名的服务器证书的 location,type,以及 password。**这些属性需要在每个利用支持 SSL 的客户端组件中的应用中设置。**
49 |
50 | #### 客户端 trust store 配置的系统属性
51 |
52 | | 属性名 | 描述 |
53 | | ---------------------------------- | ------------------------------------------------------------ |
54 | | `javax.net.ssl.trustStore` | 指定包含服务端组件的证书或者受信任根证书的文件的文件系统的路径 |
55 | | `javax.net.ssl.trustStoreType` | 指定 trust store 类型。如果这个属性没有被指定,则默认为 JKS |
56 | | `javax.net.ssl.trustStorePassword` | 指定访问 trust store 的密码 |
57 |
58 | 查看下面 [Examples]([Examples](https://logback.qos.ch/manual/usingSSL.html#Examples)) 部分,在应用启动的时候通过利用 logback 支持 SSL 的客户端组件来设置这些系统属性。
59 |
60 | ### 高级 SSL 配置
61 |
62 | 在特定的情况下,使用 JSEE 系统属性的基本 SSL 配置是不恰当的。比如,你想要在 web 应用中使用 `SSLServerSocketReceiver` 组件,你可能想要使用不同的证书为你的远程日志客户端标识你的日志服务器,而不是 web 服务器为了 web 客户端使用证书来标识自身。你可能想要在日志服务器上验证 SSL 客户端,确保只有真正授权的远程 logger 才可以连接。或者你的组织对于在组织所在的网络上使用 SSL 协议以及密码套件有严格的策略。为了满足这些需求,你需要使用 logback SSL 高级配置选项。
63 |
64 | 在配置 logback 组件支持 SSL 时,你需要在组件的配置中指定 `ssl` 属性来指定 SSL 的配置。
65 |
66 | 例如,如果你想要使用 `SSLServerSocketReceiver` 以及为日志服务器认证配置 key store 属性,你可以使用类似如下的配置:
67 |
68 | ```xml
69 |
70 |
71 |
72 |
73 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | classpath:/logging-server-keystore.jks
85 | changeit
86 |
87 |
88 |
89 |
90 |
91 | ```
92 |
93 | 该配置将 key store 的位置指定为位于应用类路径下的 *logging-server-keystore.jks*。你也可以通过 `file:` URL 来指定 key store 的位置。
94 |
95 | 如果你想要在你的应用中使用 `SSLSocketAppender`,但是不想要改变默认 trust store 使用的 JSSE `javax.net.ssl.trustStore` 属性。你可以通过如下的方式进行配置:
96 |
97 | ```xml
98 |
99 |
100 |
101 |
102 | classpath:/logging-server-truststore.jks
103 | changeit
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | ```
114 |
115 | 该配置将 key store 的位置指定为位于应用类路径下的 *logging-server-keystore.jks*。你也可以通过 `file:` URL 来指定 key store 的位置。
116 |
117 | #### SSL 配置属性
118 |
119 | JSSE 公开 了大量的可配置选项。logback 的 SSL 支持几乎所有这些可用的选项在支持 SSL 组件的配置中指定。在使用 XML 配置时,在组件的配置中,SSL 属性被嵌套在 `` 元素中引入。这个配置元素对应 [`SSLConfiguration`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/SSLConfiguration.html) 类。
120 |
121 | 当为你的组件配置 SSL 时,你仅仅只需要配置那些默认值不适合的 SSL 属性。过度指定 SSL 配置经常会导致一些难以诊断的问题。
122 |
123 | 下面的表格展示了顶层的 SSL 配置属性。许多顶层的属性还有额外的子属性,这些子属性将会顶层属性之后描述。
124 |
125 | | 属性名 | 类型 | 描述 |
126 | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
127 | | **keyManagerFactory** | [`KeyManagerFactoryFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/KeyManagerFactoryFactoryBean.html) | 指定用于创建 [`KeyManagerFactory`](http://docs.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/KeyManagerFactory.html) 的配置。如果这个属性没有配置,那么将会使用 Java 平台默认的 factory。见 [Key Manager Factory 配置](https://logback.qos.ch/manual/usingSSL.html#KeyManagerFactoryFactoryBean) |
128 | | **keyStore** | [`KeyStoreFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/KeyStoreFactoryBean.html) | 指定用于创建 [`KeyStore`](http://docs.oracle.com/javase/1.5.0/docs/api/java/security/KeyStore.html) 的配置。通过这个属性创建的 KeyStore 必须包含唯一的 X.509 证书 (包含密钥,相应的证书以及 CA 认证链)。这个证书由本地 SSL 提供给远程的 SSL。
当配置一个 SSL 客户端时 (例如,`SSLSocketAppender`),仅仅只有在远程配置需要验证客户端身份时才需要该属性。
当配置一个 SSL 服务端时 (例如,`SimpleSSLSocketServer`),该属性指定的 key store 包含了服务端证书。如果没有配置这个属性,JSSE 的 `javax.net.ssl.keyStore` 系统属性必须配置用于提供服务端 key store 的位置。查看 [定制 JSEE](https://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#InstallationAndCustomization) 来获取更多关于 JSEE 系统属性的信息。
更多的信息查看 [Key Store 配置](https://logback.qos.ch/manual/usingSSL.html#KeyStoreFactoryBean)。 |
129 | | **parameters** | [`SSLParametersConfiguration`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/SSLParametersConfiguration.html) | 在 SSL 会话中,指定各种参数。见下面的 [SSL 参数配置](https://logback.qos.ch/manual/usingSSL.html#SSLParametersConfiguration)。 |
130 | | **protocol** | `String` | 指定创建 [`SSLContext`](http://docs.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/SSLContext.html) 的 SSL 协议。见[命名规范](https://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#AppA)。如果没有配置该参数,那么将使用 Java 平台默认的协议。 |
131 | | **provider** | `String` | 指定创建 [`SSLContext`](http://docs.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/SSLContext.html) 的 JSSE 提供者。如果没有配置该参数,那么将使用 Java 平台默认的 JSSE 提供者。 |
132 | | **secureRandom** | [`SecureRandomFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/SecureRandomFactoryBean.html) | 指定创建 [`SecureRandom`](http://docs.oracle.com/javase/1.5.0/docs/api/java/security/SecureRandom.html) (安全的随机数生成器) 的配置。如果没有配置该参数,那么将使用 Java 平台默认的生成器。见下面的 [安全随机数生成配置](https://logback.qos.ch/manual/usingSSL.html#SecureRandomFactoryBean)。 |
133 | | **trustManagerFactory** | [`TrustManagerFactoryFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/TrustManagerFactoryFactoryBean.html) | 指定创建 [`TrustManagerFactory`](http://docs.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/TrustManagerFactory.html) 的配置。如果没有指定该参数,那么将使用 Java 平台默认的 factory。见下面的 [受信任的管理工厂](https://logback.qos.ch/manual/usingSSL.html#TrustManagerFactoryFactoryBean)。 |
134 | | **trustStore** | [`KeyStoreFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/KeyStoreFactoryBean.html) | 指定创建 [`KeyStore`](http://docs.oracle.com/javase/1.5.0/docs/api/java/security/KeyStore.html) 的配置,用于验证远程 SSL 的身份。通过这个属性创建的 KeyStore 应该包含一个或多个*信任锚 (trust anchors)* — keystore 中被标记为受信任的自签名证书。通常,trust store 包含自签名的 CA 证书。
通过该属性指定的 trust store 会覆盖任何通过 JSSE 的 `javax.net.ssl.trustStore` 以及平台默认的 trust store。 |
135 |
136 | #### Key Store 配置
137 |
138 | [`KeyStoreFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/KeyStoreFactoryBean.html) 指定创建包含 X.509 证书的 [`KeyStore`](http://docs.oracle.com/javase/1.5.0/docs/api/java/security/KeyStore.html) 所需要的配置。这个 factory bean 的属性能够用于 [SSL 配置](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#%E9%AB%98%E7%BA%A7-ssl-%E9%85%8D%E7%BD%AE) 中的 [keyStore](https://logback.qos.ch/manual/usingSSL.html#ssl.keyStore) 以及 [trustStore](https://logback.qos.ch/manual/usingSSL.html#ssl.trustStore) 属性。
139 |
140 | | 属性名 | 类型 | 描述 |
141 | | ------------ | -------- | ------------------------------------------------------------ |
142 | | **location** | `String` | 指定 key store 的位置 URL。使用 `file:` 指定 keystore 在文件系统中的位置。使用 `classpath:` 指定 keystore 在类路径下的位置。如果 URL 没有指定具体的策略,那么将使用 `classpath:`。 |
143 | | **password** | `String` | 指定访问 keystore 的密码 |
144 | | **provider** | `String` | 指定用于创建 `KeyStore` 的 JCA 提供者的名字。如果没有指定该属性,那么将使用 Java 平台默认的 keystore 提供者。 |
145 | | **type** | `String` | 指定 `KeyStore` 的类型。见 [命名规范](http://docs.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA)。如果没有指定该属性,那么将使用 Java 平台默认的 keystore 类型。 |
146 |
147 | #### Key Manager Factory 配置
148 |
149 | [`KeyManagerFactoryFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/KeyManagerFactoryFactoryBean.html) 指定创建 [`KeyManagerFactory`](http://docs.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/KeyManagerFactory.html) 需要的配置。通常,没有必要详细的配置 key manager factory,因为平台默认的 factory 就可以满足大部分的需求。
150 |
151 | | 属性名 | 类型 | 描述 |
152 | | ------------- | -------- | ------------------------------------------------------------ |
153 | | **algorithm** | `String` | 指定 `KeyManagerFactory` 算法的名字。见 [命名规范](http://docs.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA)。如果没有指定该属性,那么将会使用 Java 平台默认的 key manager 算法。 |
154 | | **provider** | `String` | 指定生成 `SecureRandom` 生成器的 JCA 提供者的名字。如果没有指定该属性,那么将会使用 Java 平台默认的 JSSE 提供者。 |
155 |
156 | #### Secure Random Generator 配置
157 |
158 | [`SecureRandomFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/SecureRandomFactoryBean.html) 指定创建 [`SecureRandom`](http://docs.oracle.com/javase/1.5.0/docs/api/java/security/SecureRandom.html) 生成器所需要的配置。通常,没有必要详细的配置 secure random 生成器,因为平台默认的生成器就可以满足大部分的需求。
159 |
160 | | 属性名 | 类型 | 描述 |
161 | | ------------- | -------- | ------------------------------------------------------------ |
162 | | **algorithm** | `String` | 指定 `SecureRandom` 算法的名字。见 [命名规范](http://docs.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA)。如果没有指定该属性,那么将会使用 Java 平台默认的随机数生成器 (random number generation) 算法。 |
163 | | **provider** | `String` | 指定用于创建 `SecureRandom` 生成器的 JCA 提供者。如果没有指定该属性,那么将会使用 Java 平台默认的 JSSE 提供者。 |
164 |
165 | #### SSL 参数配置
166 |
167 | [`SSLParametersConfiguration`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/SSLParametersConfiguration.html) 允许定制受允许的 SSL 协议,密码套件,以及客户端认证选项。
168 |
169 | | Property Name | Type | Description |
170 | | ------------------------ | --------- | ------------------------------------------------------------ |
171 | | **excludedCipherSuites** | `String` | 指定以逗号分隔的 SSL 密码套件的名字或者模式列表,用来在会话期间进行禁用。这个属性通过 SSL 引擎过滤密码套件。任何被该属性匹配到的密码套件都会被禁用。
以逗号分割的各个字段可以是字符串或者正则表达式。
查看密码套件的[命名规则](https://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#AppA) |
172 | | **includedCipherSuites** | `String` | 与上一个属性除了作用相反,其它都是一样 (懒得翻译了)。 |
173 | | **excludedProtocols** | `String` | 这个是表示需要排除的 SSL 协议。与上一个属性,除了作用不同,其它都一样。 |
174 | | **includedProtocols** | `String` | 与上一个协议作用相反,其它一致。 |
175 | | **needClientAuth** | `boolean` | 设置属性为 `true` 表示,服务端需要一个有效的客户端证书。当客户端组件为 `SSLSocketAppender` 时,该属性会被忽略。 |
176 | | **wantClientAuth** | `boolean` | 设置属性为 `true` 表示,服务端想要一个有效的客户端证书。当客户端组件为 `SSLSocketAppender` 时,该属性会被忽略。(与上一个属性的差异,需要自己动手去实践。因为我也不清楚😂) |
177 |
178 | #### Trust Manager Factory 配置
179 |
180 | [`TrustManagerFactoryFactoryBean`](https://logback.qos.ch/xref/ch/qos/logback/core/net/ssl/TrustManagerFactoryFactoryBean.html) 指定创建 [`TrustManagerFactory`](http://docs.oracle.com/javase/1.5.0/docs/api/javax/net/ssl/TrustManagerFactory.html) 所需要的配置。通常,没有必要明确的配置 trust manager factory,平台默认的 factory 就可以满足大部分的需要。
181 |
182 | | 属性名 | 类型 | 描述 |
183 | | ------------- | -------- | ------------------------------------------------------------ |
184 | | **algorithm** | `String` | 指定 `TrustManagerFactory` 算法的名字。见 [命名标准](https://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#AppA)。如果没有指定该属性,那么将使用 Java 平台默认的 key manager 算法。 |
185 | | **provider** | `String` | 指定创建 `SecureRandom` 生成器的 JCA 提供者的名字。如果没有指定该属性,那么将使用 Java 平台默认的 JSSE 提供者。 |
186 |
187 | ## 示例
188 |
189 | ### 使用 JSSE 系统属性
190 |
191 | JSEE 系统属性可以用来指定包含服务端 X.509 证书的 keystore 的位置以及密码。或者包含客户端组件验证服务端信任的自签名根 CA 证书的 truststore 的位置以及密码。
192 |
193 | #### 指定服务端 keystore
194 |
195 | 在运行服务端组件时,需要指定包含服务端证书的 keystore 的位置以及密码。一种方法是使用 JSSE 系统属性。下面的例子通过命令行的方式启动 `SimpleSSLSocketServer`:
196 |
197 | ```shell
198 | java -DkeyStore=/etc/logback-server-keystore.jks \
199 | -DkeyStorePassword=changeit -DkeyStoreType=JKS \
200 | ch.qos.logback.net.SimpleSSLSocketServer 6000 /etc/logback-server-config.xml
201 | ```
202 |
203 | 注意,在使用 JSSE *keyStore* 系统属性时,指定了 keystore 的路径。在指定了 *logback.xml* 的路径时,keystore 的 URL 被指定。
204 |
205 | 虽然这个示例启动了一个提供 logback 的单机服务端应用,但是同样的系统属性可以在任何使用支持 SSL 的 logback 服务端组件上指定并启动。
206 |
207 | #### 指定客户端 truststore
208 |
209 | 当使用客户端组件时,需要指定包含根 CA 证书的 truststore 的位置与密码,用于验证服务端的信任。一种方式是通过使用 JSSE 系统属性。下面的示例通过命令行启动一个名为 `com.example.MyLoggingApplication` 的应用,该应用使用一个或多个支持 SSL 的 logback 客户端组件。
210 |
211 | ```shell
212 | java -DtrustStore=/etc/logback-client-truststore.jks \
213 | -DtrustStorePassword=changeit -DtrustStoreType=JKS \
214 | com.example.MyLoggingApplication
215 | ```
216 |
217 | 注意,在使用 JSSE *trustStore* 系统属性时,指定了 truststore 的路径。在指定了 *logback.xml* 的路径时,truststore 的 URL 被指定。
218 |
219 | ### 创建以及使用自签名的服务端组件证书
220 |
221 | 为了生成自签名的证书,可以使用 JRE 自带的 *keytool* 工具。下面的指令在服务端组件的 keystore 中创建一个自签名的 X.509 证书。以及创建在客户端组件中使用的 truststore。
222 |
223 | #### 创建服务端组件证书
224 |
225 | 下面的命令在一个名为 *server.keystore* 的文件中生成自签名客户端证书。
226 |
227 | ```shell
228 | keytool -genkey -alias server -dname "CN=my-logging-server" \
229 | -keyalg RSA -validity 365 -keystore server.keystore
230 | Enter keystore password:
231 | Re-enter new password:
232 | Enter key password for
233 | (RETURN if same as keystore password):
234 | ```
235 |
236 | 在 *dname* 中使用的名字 *my-logging-server* 可以是任何你选择的有效的名字。你可能想要使用服务端主机上的全限定名。*validity* 参数可以指定从当前日期开始直到证书过期的天数。
237 |
238 | 在实际的设置中,为包含服务端证书的 keystore 选择一个强密码是非常重要的。密码用来保护服务端的密码,防止被已授权方使用。记下刚才设置的密码,因为在接下来配置服务端的时候需要使用。
239 |
240 | #### 为客户端组件创建 truststore
241 |
242 | 由于要配置客户端组件,在前一个步骤中创建的服务端证书需要从 keystore 中导出,并且导入到 truststore 中。下面的命令将会导出证书并且导入到名为 *server.truststore* 的 truststore。
243 |
244 | ```shell
245 | keytool -export -rfc -alias server -keystore server.keystore \
246 | -file server.crt
247 | Enter keystore password:
248 |
249 | keytool -import -alias server -file server.crt -keystore server.truststore
250 | Enter keystore password:
251 | Re-enter new password:
252 | Owner: CN=my-logging-server
253 | Issuer: CN=my-logging-server
254 | Serial number: 6e7eea40
255 | Valid from: Sun Mar 31 07:57:29 EDT 2013 until: Mon Mar 31 07:57:29 EDT 2014
256 |
257 | ...
258 |
259 | Trust this certificate? [no]:
260 | ```
261 |
262 | 第一个命令从 keystore 中导出服务端证书 (但是不是服务端密钥) 到一个名为 *server.crt* 的文件。第二个步骤创建一个名为 *server.truststore*,包含服务端证书的新的 truststore。
263 |
264 | 在实际的设置中,为 truststore 选择一个不同于服务端 keystore 的强密码非常的重要。记住这个密码,因为在配置客户端 appender 时需要使用。
265 |
266 | #### 配置服务端组件
267 |
268 | 拷贝 *server.keystore* 到你的服务端应用的配置中。keystore 可以放在应用的类路径下,或者放在服务器主机文件系统的某个位置。在配置文件中指定 keystore 位置 URL 时,可以使用 `classpath:` 或者 `file:`。一个示例的服务端配置如下:
269 |
270 | ```xml
271 |
272 |
273 |
274 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 | classpath:server.keystore
286 | ${server.keystore.password}
287 |
288 |
289 |
290 |
291 | ```
292 |
293 | 示例假设 keystore 放在应用类路径的根路径下。
294 |
295 | 配置文件中使用 *server.keystore.password* 替换符指定 keystore 的密码。这种方式可以避免将密码直接存储在配置文件中。例如,在启动的时候,你的应用可能会在控制台提示密码,之后在配置日志系统之前使用输入的密码将 *server.keystore.password* 设置为系统属性。
296 |
297 | #### 配置客户端组件
298 |
299 | 你需要将 *server.truststore* 文件拷贝到使用支持 SSL 组件充当客户端角色的每个应用的配置中。truststore 可以放置在应用的类路径下,或者文件系统的某个地方。可以通过 `classpath:` 或者 `file:` 来指定 truststore 位置的 URL。客户端 appender 的配置示例如下:
300 |
301 | > Example:
302 |
303 | ```xml
304 |
305 |
306 | ${host}
307 |
308 |
309 | classpath:server.truststore
310 | ${server.truststore.password}
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 | ```
320 |
321 | 示例中假设 truststore 位于应用类路径的根路径下。
322 |
323 | 配置中使用 *server.truststore.password* 替换符来指定 truststore 的密码。这种方式可以避免直接将密码存储在配置文件中。例如,应用启动时会在控制台提示密码,之后可以在配置日志系统之前通过输入密码将 *server.truststore.password* 设置为系统属性。
324 |
325 | ## 审核 SSL 配置
326 |
327 | 在需要安全通行的设置中,经常需要审核组件的 SSL 配置,验证与本地策略的一致性。在 logback 初始化时,logback 中的 SSL 通过提供 SSL 配置的详细日志来满足这一需求。可以在配置中使用 `debug` 属性来开启这个功能。
328 |
329 | ```xml
330 |
331 |
332 | ...
333 |
334 |
335 | ```
336 |
337 | 当使用了 debug 属性,在日志系统初始化时,所有与 SSL 配置产生的相关信息都会被打印出来。一个典型的示例如下:
338 |
339 | > Example:
340 |
341 | ```java
342 | 06:46:31,941 |-INFO in SSLServerSocketReceiver@4ef18d37 - SSL protocol 'SSL' provider 'SunJSSE version 1.6'
343 | 06:46:31,967 |-INFO in SSLServerSocketReceiver@4ef18d37 - key store of type 'JKS' provider 'SUN version 1.6': file:src/main/java/chapters/appenders/socket/ssl/keystore.jks
344 | 06:46:31,967 |-INFO in SSLServerSocketReceiver@4ef18d37 - key manager algorithm 'SunX509' provider 'SunJSSE version 1.6'
345 | 06:46:31,973 |-INFO in SSLServerSocketReceiver@4ef18d37 - secure random algorithm 'SHA1PRNG' provider 'SUN version 1.6'
346 | 06:46:32,755 |-INFO in SSLParametersConfiguration@4a6f19d5 - enabled protocol: SSLv2Hello
347 | 06:46:32,755 |-INFO in SSLParametersConfiguration@4a6f19d5 - enabled protocol: SSLv3
348 | 06:46:32,755 |-INFO in SSLParametersConfiguration@4a6f19d5 - enabled protocol: TLSv1
349 | 06:46:32,756 |-INFO in SSLParametersConfiguration@4a6f19d5 - enabled cipher suite: SSL_RSA_WITH_RC4_128_MD5
350 | 06:46:32,756 |-INFO in SSLParametersConfiguration@4a6f19d5 - enabled cipher suite: SSL_RSA_WITH_RC4_128_SHA
351 | 06:46:32,756 |-INFO in SSLParametersConfiguration@4a6f19d5 - enabled cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA
352 | ```
353 |
354 | 为了简便起见,这里的输出被截断了一部分。但是包含了协议、提供者、算法、密码套件以及在配置中所使用的 keystore 与 truststore 位置。
355 |
356 | 虽然没有一个审核日志是敏感的,但是为了安全,在实际的设置中,在配置被验证之后,日志信息不应该被启用。将 `debug` 属性移除,或者设置为 `false`,将禁用日志审核。
357 |
358 | ## 解决 SSL 异常
359 |
360 | 当 SSL 配置错误时,普遍的结果是客户端与服务端组件不能正常的通常。当客户端尝试连接服务端时,这个问题通常会被双方抛出的异常表现出来。
361 |
362 | 异常消息内容的不容取决于你查看的是客户端的日志还是服务端的日志。最主要的原因是在会话期间,错误报告受限制于内部的协议。由于这种情况,为了定位会话问题,需要查看客户端与服务端的日志。
363 |
364 | ### Server's Certificate is Not Available
365 |
366 | 当启动服务端组件时,你会在日志中看到如下的异常:
367 |
368 | *javax.net.ssl.SSLException: No available certificate or key corresponds to the SSL cipher suites which are enabled*
369 |
370 | 大部分情况下表示你没有配置包含服务端密钥的 keystore 以及相应的证书。
371 |
372 | #### 解决办法
373 |
374 | 使用 [keystore 系统属性](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#%E6%9C%8D%E5%8A%A1%E7%AB%AF-key-store-%E9%85%8D%E7%BD%AE%E7%9A%84%E7%B3%BB%E7%BB%9F%E5%B1%9E%E6%80%A7) 或者服务端组件 `ssl` 属性中的 [keyStore](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7) 属性。你必须指定包含服务端密钥以及证书的 keystore 的路径以及密码。
375 |
376 | ### Client Does Not Trust the Server
377 |
378 | 当客户端尝试连接服务端时,在日志中看到如下的异常:
379 |
380 | *javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed*
381 |
382 | 这个问题表示服务端展示的证书,客户端不相信。大部分情况下是使用自签名的证书 (或者服务端证书由你组织内部的认证机构所签名) 并且你还没有配置客户端,导致它引用了包含服务端自签名证书 (或者你的服务器证书是由 CA 签名的受信任的根证书) 的 truststore。
383 |
384 | 这个问题还会发生在服务端证书过期或者被取消的情况下。在客户端每次尝试进行连接时,你在服务端的日志中会看到如下的异常信息:
385 |
386 | *javax.net.ssl.SSLHandshakeException: Received fatal alert: ...*
387 |
388 | 异常信息的余下部分通常会提供一个代码来表明为什么客户端拒绝服务端证书:
389 |
390 | | 代码 | 描述 |
391 | | --------------------- | -------------------------------------------- |
392 | | `certificate_unknown` | 通常表示客户端 truststore 没有被正确的配置 |
393 | | `certificate_expired` | 表示服务端证书已过期,需要更换 |
394 | | `certificate_revoked` | 表示颁发的 CA 已经撤销了服务端证书,需要更换 |
395 |
396 | #### 解决办法
397 |
398 | 如果服务端日志显示 `certificate_unknown`,那么使用 [truststore 系统属性](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#%E5%AE%A2%E6%88%B7%E7%AB%AF-trust-store-%E9%85%8D%E7%BD%AE%E7%9A%84%E7%B3%BB%E7%BB%9F%E5%B1%9E%E6%80%A7) 或者 appender 组件 `ssl` 属性中的 [trustStore](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7) 属性,指定包含服务端自签名证书或者颁发的 CA 根证书的 truststore 的路径以及密码。
399 |
400 | 如果服务端日志显示 `certificate_expired` 或者 `certificate_revoked`,表示服务端需要一个新的证书。新的证书以及相关的密钥需要在服务端配置的 keystore 中更换。并且,如果使用自签名服务端证书,那么还需要更换在客户端 appender 中配置的 truststore 服务端证书。
401 |
402 | ### Server Does Not Trust the Client
403 |
404 | 注意:**这个问题仅仅发生在你明确的配置服务端请求客户端证书时。(使用 [needClientAuth](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 或者 [wantClientAuth](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 属性)。**
405 |
406 | 当客户端尝试连接日志服务器时,可以在客户端日志中看到如下的异常信息:
407 |
408 | *javax.net.ssl.SSLHandshakeException: Received fatal alert: ...*
409 |
410 | 异常信息的其余部分会提供一个代码表明为什么服务端拒绝客户端证书。
411 |
412 | | 代码 | 描述 |
413 | | --------------------- | -------------------------------------------- |
414 | | `certificate_unknown` | 通常表示服务端 truststore 没有正确的配置 |
415 | | `certificate_expired` | 表示客户端证书已经过期,需要更换 |
416 | | `certificate_revoked` | 表示颁发的 CA 已经撤销了客户端证书,需要更换 |
417 |
418 | #### 解决办法
419 |
420 | 如果客户端日志信息显示 `bad_certificate`,那么使用 [truststore 系统属性](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#%E5%AE%A2%E6%88%B7%E7%AB%AF-trust-store-%E9%85%8D%E7%BD%AE%E7%9A%84%E7%B3%BB%E7%BB%9F%E5%B1%9E%E6%80%A7) 或者服务端组件 `ssl` 属性中的 [trustStore](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7) 属性,指定包含客户端自签名证书或者颁发的 CA 根证书的 truststore 的路径以及密码。
421 |
422 | 如果服务端日志信息显示 `certificate_expired` 或者 `certificate_revoked`,表示客户端需要一个新的证书。需要更换在客户端配置中指定的 keystore 的新证书以及相应的密钥。
423 |
424 | ### Client and Server Cannot Agree on a Protocol
425 |
426 | 注意:**这个问题仅仅只在你的配置中明确的配置了 [excludedProtocols](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 或者 [includedProtocols](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) SSL 协议时。**
427 |
428 | 当客户端尝试连接服务端时,你会在日志中看到如下异常信息:
429 |
430 | *javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure*
431 |
432 | 服务端的日志信息通常更加详细,例如:
433 |
434 | *javax.net.ssl.SSLHandshakeException: SSLv2Hello is disabled*
435 |
436 | 通常,表示你已经排除其中的一个协议。
437 |
438 | #### 解决办法
439 |
440 | 检查服务端与客户端指定的 [excludedProtocols](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 以及 [includedProtocols](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 属性的值。
441 |
442 | ### Client and Server Cannot Agree on a Cipher Suite
443 |
444 | 注意:**通常这个问题只发生在你明确的在配置文件中指定了 [excludedCipherSuites](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 或者 [includedCipherSuites](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) SSL 密码套件时。**
445 |
446 | 当客户端尝试去连接服务端时,你会在日志中看到如下的异常信息:
447 |
448 | *javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure*
449 |
450 | 服务端的日志信息通常会更加详细:
451 |
452 | *javax.net.ssl.SSLHandshakeException: no cipher suites in common*
453 |
454 | 这意味着你已经在服务端与客户端配置了密码套件,但是它们各自密码套件的交集为空。
455 |
456 | #### 解决办法
457 |
458 | 检查服务端与客户端指定的 [excludedCipherSuites](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 以及 [includedCipherSuites](https://github.com/Volong/logback-chinese-manual/blob/master/15%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9A%E4%BD%BF%E7%94%A8%20SSL.md#ssl-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) 属性的值。
459 |
460 |
--------------------------------------------------------------------------------
/07第七章:Filters.md:
--------------------------------------------------------------------------------
1 | 在之前的章节中介绍的[方法打印以及基本选择规则](https://github.com/Volong/logback-chinese-manual/blob/master/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%EF%BC%9A%E6%9E%B6%E6%9E%84.md#%E6%96%B9%E6%B3%95%E6%89%93%E5%8D%B0%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E9%80%89%E6%8B%A9%E8%A7%84%E5%88%99)是 logback-classic 的核心。在这章中,将介绍其它的过滤方法。
2 |
3 | logback 过滤器基于三元逻辑,允许它们组装或者链接在一起组成一个任意复杂的过滤策略。它们在很大程度上受到 Linux iptables 的启发。
4 |
5 | ## 在 logback-classic 中
6 |
7 | 在 logback-classic 中,有两种类型的过滤器,regular 过滤器以及 turbo 过滤器。
8 |
9 | ### Regular 过滤器
10 |
11 | reqular 过滤器继承自 [`Filter`](https://logback.qos.ch/xref/ch/qos/logback/core/filter/Filter.html) 这个抽象类。本质上它由一个单一的 `decide()` 方法组成,接收一个 `ILoggingEvent` 实例作为参数。
12 |
13 | 过滤器通过一个有序列表进行管理,并且基于三元逻辑。每个过滤器的 `decide(ILoggingEvent event)` 被依次调用。这个方法返回 [`FilterReply`](https://logback.qos.ch/xref/ch/qos/logback/core/spi/FilterReply.html) 枚举值中的一个, `DENY`, `NEUTRAL` 或者 `ACCEPT`。如果 `decide()` 方法返回 `DENY`,那么日志事件会被丢弃掉,并且不会考虑后续的过滤器。如果返回的值是 `NEUTRAL`,那么才会考虑后续的过滤器。如果没有其它的过滤器了,那么日志事件会被正常处理。如果返回值是 `ACCEPT`,那么会跳过剩下的过滤器而直接被处理。
14 |
15 | 在 logback-classic 中,过滤器可以被直接添加到 `Appender` 实例上。通过将一个或者多个过滤器添加到 appender 上,你可以通过任意标准来过滤日志事件。例如,日志消息的内容,MDC 的内容,时间,或者日志事件的其它部分。
16 |
17 | ### 实现你自己的过滤器
18 |
19 | 创建一个自己的过滤器非常的简单。只需要继承 `Filter` 并且实现 `decide()` 方法就可以了。
20 |
21 | 如下所示的 SampleFilter 就是一个简单的例子。如果日志事件包含字符 "sample", `decide` 方法返回 ACCEPT。对于其他的日志事件,则返回 NEUTRAL。
22 |
23 | 下面是关于将 `SampleFilter` 添加到 `ConsoleAppender` 上的配置示例:
24 |
25 | > Example: *SampleFilterConfig.xml*
26 |
27 | ```xml
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | %-4relative [%thread] %-5level %logger - %msg%n
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```
45 |
46 | 在 logback 配置框架 Joran 的帮助下,为过滤器指定属性或者子组件也变得更加的简单。在过滤器类中添加相应的 set 方法,通过 `` 元素嵌套一个以属性命名的 xml 元素中指定属性的值。
47 |
48 | 通常情况下,过滤器的逻辑由两个正交的部分组成,match/mismatch 的检验以及基于 match/mismatch 的返回值。例如,对于给定的检验,消息等于 "foobar",一个过滤器在 match 的情况下返回 ACCEPT,在 mismatch 的情况下返回 NEUTRAL。另一个过滤可能在 match 的情况下返回 NEUTRAL,在 mismatch 的情况下返回 DENY。
49 |
50 | 注意这种正交,logback 附带了一个 [`AbstractMatcherFilter`](https://logback.qos.ch/xref/ch/qos/logback/core/filter/AbstractMatcherFilter.html) 类,提供了一个有用的骨架用来指定在 match 与 mismatch 情况下的返回值,这两个属性名分别叫做 *OnMatch* 与 *OnMismatch*。logback 中大部分的 regular 过滤器都源于 `AbstractMatcherFilter`。
51 |
52 | ### LevelFilter
53 |
54 | [`LevelFilter`](https://logback.qos.ch/xref/ch/qos/logback/classic/filter/LevelFilter.html) 基于级别来过滤日志事件。如果事件的级别与配置的级别相等,过滤器会根据配置的 `onMatch` 与 `onMismatch` 属性,接受或者拒绝事件。如下是一个简单的示例:
55 |
56 | > Example: *levelFilterConfig.xml*
57 |
58 | ```xml
59 |
60 |
61 |
62 | INFO
63 | ACCEPT
64 | DENY
65 |
66 |
67 |
68 | %-4relative [%thread] %-5level %logger{30} - %msg%n
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | ```
77 |
78 | ### ThresholdFilter
79 |
80 | [`ThresholdFilter`](https://logback.qos.ch/xref/ch/qos/logback/classic/filter/ThresholdFilter.html) 基于给定的临界值来过滤事件。如果事件的级别等于或高于给定的临界值,当调用 `decide()` 时,`ThresholdFilter` 将会返回 NEUTRAL。但是事件的级别低于临界值将会被拒绝。下面是一个简单的例子:
81 |
82 | > Example: *thresholdFilterConfig.xml*
83 |
84 | ```xml
85 |
86 |
88 |
89 |
90 | INFO
91 |
92 |
93 |
94 | %-4relative [%thread] %-5level %logger{30} - %msg%n
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | ```
103 |
104 | ## EvaluatorFilter
105 |
106 | [`EvaluatorFilter`](https://logback.qos.ch/xref/ch/qos/logback/core/filter/EvaluatorFilter.html) 是一个通用的过滤器,它封装了一个 `EventEvaluator`。顾名思义,[`EventEvaluator`](https://logback.qos.ch/xref/ch/qos/logback/core/boolex/EventEvaluator.html) 根据给定的标准来评估给定的事件是否符合标准。在 match 和 mismatch 的情况下,`EvaluatorFilter` 将会返回 `onMatch` 或 `onMismatch` 指定的值。
107 |
108 | 注意 `EventEvaluator` 是一个抽象类。你可以通过继承 `EventEvaluator` 来实现自己事件评估逻辑。
109 |
110 | ### GEventEvaluator
111 |
112 | [GEventEvaluator](https://logback.qos.ch/xref/ch/qos/logback/classic/boolex/GEventEvaluator.html) 是 [`EventEvaluator`](https://logback.qos.ch/xref/ch/qos/logback/core/boolex/EventEvaluator.html) 具体的实现,它采用 Groovy 表达式作为评估的标准。我们把 Groovy 表达式称为 "Groovy 评估表达式"。Groogy 评估表达式是目前为止进行事件过滤最灵活的方式。`GEventEvaluator` 需要 Groovy 运行环境。参考[相关部分](https://logback.qos.ch/setup.html#groovy)在类路径下添加 Groovy 运行环境。
113 |
114 | 评估表达式在解析配置文件期间被动态编译。作为用户,不需要考虑实际的情况。但是,你需要确保你的 Groovy 表达式是有效的。
115 |
116 | 评估表达式作用于当前的日志事件。logback 会自动将 [ILoggingEvent](https://logback.qos.ch/apidocs/ch/qos/logback/classic/spi/ILoggingEvent.html) 类型的日志事件作为变量插入,引用到 'event' 或者它的简称 'e'。TRACE, DEBUG, INFO, WARN 以及 ERROR 也能够被导入到表达式的范围中。所以,"event.level == DEBUG" 与 "e.level == DEBUG" 是等价的。只有当当前日志事件的级别为 DEBUG 时,Groovy 表达式才会返回 `true`。对于其它的级别比较操作,应该通过 `toInt()` 操作将 level 字段转变为整型。
117 |
118 | 下面是一个比较复杂的例子:
119 |
120 | ```xml
121 |
122 |
123 |
124 |
125 |
126 |
127 | e.level.toInt() >= WARN.toInt() &&
128 | !(e.mdc?.get("req.userAgent") =~ /Googlebot|msnbot|Yahoo/ )
129 |
130 |
131 | DENY
132 | NEUTRAL
133 |
134 |
135 |
136 | %-4relative [%thread] %-5level %logger - %msg%n
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | ```
146 |
147 | 上面的过滤器会让级别在 WARN 及以上的日志事件在控制台显示,除非是由于来自 Google,MSN,Yahoo 的网络爬虫导致的错误。它通过检查与事件相关的 MDC 包含 "req.userAgent" 的值是否匹配 `/Googlebot|msbbot|Yahoo/` 正则表达式。因为 MDC 的映射可能为 null,所以我们使用 Groovy 的[安全解引用操作符](http://groovy.codehaus.org/Null+Object+Pattern),也就是 `?.` 操作符。这个相等的逻辑在 Java 中的表达式更长。
148 |
149 | 如果你好奇 user agent 标识符作为值怎样被插入到 key 为 "req.userAgent " 的 MDC 中,那么就会涉及到 logback 为了这个目的附带了一个名为 [`MDCInsertingServletFilter`](https://logback.qos.ch/manual/mdc.html#mis) 的 servlet 过滤器。它将会在接下来的章节中描述。
150 |
151 | ### JaninoEventEvaluator
152 |
153 | logback-classic 附带的另外一个 `EventEvaluator` 的具体实现名为 [JaninoEventEvaluator](https://logback.qos.ch/xref/ch/qos/logback/classic/boolex/JaninoEventEvaluator.html),它接受任意返回布尔值的 Java 代码块作为评判标准。我们把这种 Java 布尔表达式称为 "*评估表达式*"。评估表达式在事件过滤中可以更加的灵活。`JaninoEventEvaluator` 需要 [Janino 类库](http://docs.codehaus.org/display/JANINO/Home)。请参见[相关章节](https://logback.qos.ch/setup.html#janino)进行设置。跟 `JaninoEventEvaluator` 相比,`GEventEvaluator` 使用 Groovy 语言,使用起来非常方便。但是 `JaninoEventEvaluator` 将使用运行更快的等效表达式。
154 |
155 | 评估表达式在解析配置文件期间被动态编译。作为用户,不需要考虑实际的情况。但是,你需要确保你的 Java 表达式是有效的,保证它的评估结果为 true 或 false。
156 |
157 | 评估表达式对当前日志事件进行评估。logback-classic 自动导出日志事件的各种字段作为变量,为了可以从评估表达式访问。这些导出的变量是大小写敏感的,如下表所示:
158 |
159 | | 名字 | 类型 | 描述 |
160 | | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
161 | | event | `LoggingEvent` | 日志请求的原始日志事件。下面所有的变量都来自这个日志事件。例如,`event.getMessage()` 返回的字符串跟下面的 `message` 变量返回的字符串一样。 |
162 | | message | `String` | 日志请求的原始信息。例如,对于 logger *I*,当你写的是 I.info("Hello {}", name); 时,name 的值被指定为 "Alice",消息就为 "Hello {}"。 |
163 | | formattedMessage | `String` | 日志请求中格式化后的消息。例如,对于 logger *I*,当你写的是 I.info("Hello {}", name); 时,name 的值被指定为 "Alice",格式化后的消息就为 "Hello Alice"。 |
164 | | logger | `String` | logger 的名字 |
165 | | loggerContext | [`LoggerContextVO`](https://logback.qos.ch/xref/ch/qos/logback/classic/spi/LoggerContextVO.html) | 日志事件属于 logger 上下文中哪个受限的视图 (值对象) |
166 | | level | `int` | 事件级别对应的 int 值。用来创建包含级别的表达式。默认值是 DEBUG,INFO,WARN 以及 ERROR 也是有效的。所以 *level > INFO* 是有效的表达式。 |
167 | | timeStamp | `long` | 日志事件创建的时间 |
168 | | marker | `Marker` | 与日志请求相关的 `Marker` 对象。注意,marker 可能会为 null,因此你需要对这种情况进行检查,进而避免 `NullPointerException`。 |
169 | | mdc | `Map` | 创建日志事件时包含的所有的 MDC 值的一个映射。可以通过 *mdc.get("myKey")* 来获取 MDC 中对应的值。在 0.9.30 版本的 logback-classic,mdc 变量永远不会为 null。
`java.util.Map` 类型是非参数化的,因为 Janino 不支持泛型。因此,`mdc.get()` 返回值的类型是 `Object` 而不是 `String`。但是可以将返回值强制转换为 `String`。例如, `((String) mdc.get("k")).contains("val")`。 |
170 | | throwable | java.lang.Throwable | 如果日志事件没有相关的异常,那么变量 "throwable" 的值为 null。"throwable" 不可以被序列化。所以在远程服务器上,这个值永远为 null。想要使用与位置无关的表达式,可以使用下面的 `throwableProxy`。 |
171 | | throwableProxy | [`IThrowableProxy`](https://logback.qos.ch/xref/ch/qos/logback/classic/spi/IThrowableProxy.html) | 日志事件的异常代理。如果日志事件没有相关的异常,那么 `throwableProxy` 的值为 null。与 "throwable" 相反,即使在远程服务器上序列化之后,日志事件相关的异常也不会为 null。 |
172 |
173 | 下面是具体的例子。
174 |
175 | > Example: *basicEventEvaluator.xml*
176 |
177 | ```xml
178 |
179 |
180 |
181 |
182 |
183 | return message.contains("billing");
184 |
185 | NEUTRAL
186 | DENY
187 |
188 |
189 |
190 | %-4relative [%thread] %-5level %logger - %msg%n
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 | ```
200 |
201 | 上面的配置将 `EvaluatorFilter` 添加到 `ConsoleAppender`。一个类型为 `JaninoEventEvaluator` 的 evaluator 之后被注入到 `EvaluatorFilter` 中。`
254 |
255 | if(logger.startsWith("org.apache.http"))
256 | return true;
257 |
258 | if(mdc == null || mdc.get("entity") == null)
259 | return false;
260 |
261 | String payee = (String) mdc.get("entity");
262 |
263 | if(logger.equals("org.apache.http.wire") &&
264 | payee.contains("someSpecialValue") &&
265 | !message.contains("someSecret")) {
266 | return true;
267 | }
268 |
269 | return false;
270 |
271 |
272 | ```
273 |
274 | ## Matchers
275 |
276 | 虽然可以通过调用 `String` 类的 [matches()](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html#matches%28java.lang.String%29) 方法来进行模式匹配,但是每次调用 filter 都需要耗费时间重新编译一个新的 `Pattern` 对象。为了消除这种影响,你可以预先定义一个或者多个 [Matcher](https://logback.qos.ch/xref/ch/qos/logback/core/boolex/Matcher.html) 对象。一旦定义了一个 matcher,就可以在评估表达式中重复使用了。
277 |
278 | 通过一个简单的例子来说明这一点:
279 |
280 | > Example: *evaluatorWithMatcher.xml*
281 |
282 | ```xml
283 |
284 |
285 |
286 |
287 |
288 |
289 | odd
290 |
291 | statement [13579]
292 |
293 |
294 | odd.matches(formattedMessage)
295 |
296 | NEUTRAL
297 | DENY
298 |
299 |
300 | %-4relative [%thread] %-5level %logger - %msg%n
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 | ```
309 |
310 | 通过 *evaluatorWithMatcher.xml* 运行:
311 |
312 | ```bash
313 | java chapters.filters.FilterEvents src/main/java/chapters/filters/evaluatorWithMatcher.xml
314 | ```
315 |
316 | 将会得到:
317 |
318 | ```java
319 | 260 [main] INFO chapters.filters.FilterEvents - logging statement 0
320 | 264 [main] INFO chapters.filters.FilterEvents - logging statement 2
321 | 264 [main] INFO chapters.filters.FilterEvents - logging statement 4
322 | 266 [main] ERROR chapters.filters.FilterEvents - billing statement 6
323 | 266 [main] INFO chapters.filters.FilterEvents - logging statement 8
324 | ```
325 |
326 | 如果你想定义其它的 matcher,可以继续增加 `` 元素。
327 |
328 | ## TurboFilters
329 |
330 | `TurboFilter` 对象都继承 [`TurboFilter`](https://logback.qos.ch/xref/ch/qos/logback/classic/turbo/TurboFilter.html) 抽象类。对于 regular 过滤器,它们使用三元逻辑来返回对日志事件的评估。
331 |
332 | 总之,它们跟之前提到的过滤工作原理差不多。主要的不同点在于 `Filter` 与 `TurboFilter` 对象。
333 |
334 | `TurboFilter` 对象被绑定刚在 logger 上下文中。因此,在使用给定的 appender 以及每次发出的日志请求都会调用 `TurboFilter` 对象。因此,turbo 过滤器可以为日志事件提供高性能的过滤,即使是在事件被创建之前。
335 |
336 | ### 实现自己的 TurboFilter
337 |
338 | 想要创建自己的 `TurboFilter` 组件,只需要继承 `TurboFilter` 这个抽象类就可以了。跟之前的一样,想要实现定制的过滤器对象,开发自定义的 `TurboFilter`,只需要实现 `decide()` 方法就可以了。下一个例子,我们会创建一个稍微复杂一点的过滤器:
339 |
340 | > Example: *SampleTurboFilter.java*
341 |
342 | ```java
343 | package chapters.filters;
344 |
345 | import org.slf4j.Marker;
346 | import org.slf4j.MarkerFactory;
347 |
348 | import ch.qos.logback.classic.Level;
349 | import ch.qos.logback.classic.Logger;
350 | import ch.qos.logback.classic.turbo.TurboFilter;
351 | import ch.qos.logback.core.spi.FilterReply;
352 |
353 | public class SampleTurboFilter extends TurboFilter {
354 |
355 | String marker;
356 | Marker markerToAccept;
357 |
358 | @Override
359 | public FilterReply decide(Marker marker, Logger logger, Level level,
360 | String format, Object[] params, Throwable t) {
361 |
362 | if (!isStarted()) {
363 | return FilterReply.NEUTRAL;
364 | }
365 |
366 | if ((markerToAccept.equals(marker))) {
367 | return FilterReply.ACCEPT;
368 | } else {
369 | return FilterReply.NEUTRAL;
370 | }
371 | }
372 |
373 | public String getMarker() {
374 | return marker;
375 | }
376 |
377 | public void setMarker(String markerStr) {
378 | this.marker = markerStr;
379 | }
380 |
381 | @Override
382 | public void start() {
383 | if (marker != null && marker.trim().length() > 0) {
384 | markerToAccept = MarkerFactory.getMarker(marker);
385 | super.start();
386 | }
387 | }
388 | }
389 | ```
390 |
391 | `TurboFilter` 接受一个指定的 marker,如果 marker 没有被找到,那么过滤器会将日志事件传递给过滤器链中的下一个过滤器。
392 |
393 | 为了更加灵活,允许在配置文件指定 marker 用于检测,因此可以使用 get 和 set 方法。我们还可以通过实现 `start()` 方法来检查在配置过程中,指定的选项是否满足。
394 |
395 | 下面的配置充分利用了我们新创建的 `TurboFilter`。
396 |
397 | > Example: *sampleTurboFilterConfig.xml*
398 |
399 | ```xml
400 |
401 |
402 | sample
403 |
404 |
405 |
406 |
407 |
408 | %-4relative [%thread] %-5level %logger - %msg%n
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 | ```
418 |
419 | loback-classic 附带了几个 `TurboFilter` 类可以开箱即用。[`MDCFilter`](https://logback.qos.ch/xref/ch/qos/logback/classic/turbo/MDCFilter.html) 用来检查给定的值在 MDC 中是否存在。[`DynamicThresholdFilter`](https://logback.qos.ch/apidocs/ch/qos/logback/classic/turbo/DynamicThresholdFilter.html) 根据 MDC key/level 相关的阀值来进行过滤。[`MarkerFilter`](https://logback.qos.ch/xref/ch/qos/logback/classic/turbo/MarkerFilter.html) 用来检查日志请求中指定的 marker 是否存在。
420 |
421 | 下面的例子使用了 `MDCFilter` 与 `MarkerFilter`。
422 |
423 | > Example: *turboFilters.xml*
424 |
425 | ```xml
426 |
427 |
428 |
429 | username
430 | sebastien
431 | ACCEPT
432 |
433 |
434 |
435 | billing
436 | DENY
437 |
438 |
439 |
440 |
441 | %date [%thread] %-5level %logger - %msg%n
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 | ```
450 |
451 | 执行以下命令:
452 |
453 | ```bash
454 | java chapters.filters.FilterEvents src/main/java/chapters/filters/turboFilters.xml
455 | ```
456 |
457 | 在之前我们看到 [`FilterEvents`](https://logback.qos.ch/xref/chapters/filters/FilterEvents.html) 输出了 10 条日志请求,编号 0 到 9。除了第 3 条与第 6 条,所有的请求都是 INFO 级别的,与 root logger 的级别一致。第 3 条日志请求是 `DEBUG` 级别的,在有效级别之下。但是,因为 MDC 的 key "username" 在第三条请求之前设置为 "sebastien",之后才被移除,所以 `MDCFilter` 接受这条请求 (仅仅只有这条请求)。第 6 条请求的级别为 `ERROR`,被标记为 "billing"。因此,它会被 `MarkerFilter` (配置文件中第二个 turbo 过滤器) 拒绝。
458 |
459 | 因此,`FilterEvents` 通过 *turboFilters.xml* 输出的信息如下:
460 |
461 | ```java
462 | 2018-08-20 23:19:28,807 [main] INFO chapters.filters.FilterEvents - logging statement 0
463 | 2018-08-20 23:19:28,810 [main] INFO chapters.filters.FilterEvents - logging statement 1
464 | 2018-08-20 23:19:28,810 [main] INFO chapters.filters.FilterEvents - logging statement 2
465 | 2018-08-20 23:19:28,810 [main] DEBUG chapters.filters.FilterEvents - logging statement 3
466 | 2018-08-20 23:19:28,810 [main] INFO chapters.filters.FilterEvents - logging statement 4
467 | 2018-08-20 23:19:28,810 [main] INFO chapters.filters.FilterEvents - logging statement 5
468 | 2018-08-20 23:19:28,810 [main] INFO chapters.filters.FilterEvents - logging statement 7
469 | 2018-08-20 23:19:28,811 [main] INFO chapters.filters.FilterEvents - logging statement 8
470 | 2018-08-20 23:19:28,811 [main] INFO chapters.filters.FilterEvents - logging statement 9
471 | ```
472 |
473 | 可以看到,第 3 条日志请求,本来不应该被展示出来,因为我们仅仅只关注 *INFO* 级别的请求,但是它匹配了第一个 `TurboFilter`,所以被接受了。
474 |
475 | 第 6 条日志请求,它是 *ERROR* 级别的日志,应该被显示。但是因为满足第二个 `TurboFilter`,它的 `OnMatch` 设置为 *DENY*,所以第 6 条请求不会被展示。
476 |
477 | ### DuplicateMessageFilter
478 |
479 | `DuplicateMessageFilter` 可以拿出来单独阐述。这个过滤器检测重复的消息,在重复了一定次数之后,丢弃掉重复的消息。
480 |
481 | 这个过滤器使用字符串是否相等来检查是否重复。不会检查非常相似,仅仅只差几个字符的字符串。例如:
482 |
483 | ```java
484 | logger.debug("Hello "+name0);
485 | logger.debug("Hello "+name1);
486 | ```
487 |
488 | 如果 `name0` 与 `name1` 有不同的值,那么两个 "Hello" 消息会被认为不相关。根据用户的需要,将会可能会支持相似字符串的检查,限制相似字符串的重复,而不是完全相同的。
489 |
490 | 但是在参数化日志请求中,只考虑原始消息。例如,下面两条日志请求,原始消息为 "Hello {}",它们被认为是想相等的,因此被认为是重复出现。
491 |
492 | ```javascript
493 | logger.debug("Hello {}.", name0);
494 | logger.debug("Hello {}.", name1);
495 | ```
496 |
497 | 可以通过 `AllowedRepetitions` 属性来指定允许重复的次数。如果这个属性被设置为 1,那么第二条以及后续的日志消息都会被丢弃掉。类似的,如果被设置为 2,那么第三条及后续的日志消息会被丢弃掉。这个值默认设置为 5。
498 |
499 | 为了检测重复,过滤器需要在内部的缓存中保留对旧消息的引用。通过 `CacheSize` 来控制缓存的大小。默认情况下,这个值为 100。
500 |
501 | > Example: *duplicateMessage.xml*
502 |
503 | ```xml
504 |
505 |
506 |
507 |
508 |
509 |
510 | %date [%thread] %-5level %logger - %msg%n
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 | ```
519 |
520 | `FilterEvents` 通过 `duplicateMessage.xml` 配置后输出如下:
521 |
522 | ```java
523 | 2018-08-21 09:09:22,036 [main] INFO chapters.filters.FilterEvents - logging statement 0
524 | 2018-08-21 09:09:22,041 [main] INFO chapters.filters.FilterEvents - logging statement 1
525 | 2018-08-21 09:09:22,041 [main] INFO chapters.filters.FilterEvents - logging statement 2
526 | 2018-08-21 09:09:22,041 [main] INFO chapters.filters.FilterEvents - logging statement 4
527 | 2018-08-21 09:09:22,041 [main] INFO chapters.filters.FilterEvents - logging statement 5
528 | 2018-08-21 09:09:22,050 [main] ERROR chapters.filters.FilterEvents - billing statement 6
529 | ```
530 |
531 | "logging statement 0" 是消息 "logging statement {}"j 第一次出现。"logging statement 1" 是第一次重复。"logging statement 2" 是第二次重复。有趣的是,虽然 "logging statement 3" 的级别为 *DEBUG*,为第三次重复。但是根据[方法打印以及基本选择规则](https://github.com/Volong/logback-chinese-manual/blob/master/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%EF%BC%9A%E6%9E%B6%E6%9E%84.md#%E6%96%B9%E6%B3%95%E6%89%93%E5%8D%B0%E4%BB%A5%E5%8F%8A%E5%9F%BA%E6%9C%AC%E9%80%89%E6%8B%A9%E8%A7%84%E5%88%99),它被丢弃了。这也说明了 turbo 过滤器会在其它过滤器之前调用,包括在基本选择规则之前。因此 `DuplicateMessageFilter` 认为 "logging statement 3" 是第三次重复,而不会管它是否会在之后过滤器链的处理中被丢弃掉。"logging statement 4" 是第四次重复。"logging statement 5" 是第五次。因此默认的重复次数是 5,所以之后的语句都会被丢弃掉。(注:指的是 "logging statement {}")。
532 |
533 | # 在 logback-access 中
534 |
535 | logback-access 提供了 logback-classic 提供的大部分功能。特别地,`Filter` 对象同样是有效的,并且以同样的方式工作,就像 logback-classic 的副本一样,但是有一个显著的区别。logback-access 过滤器对 [`AccessEvent`](https://logback.qos.ch/xref/ch/qos/logback/access/spi/AccessEvent.html) 实例起作用,而不是 `LoggingEvent` 实例。目前,logback-access 只提供了以下有限的过滤器。如果你想建议添加额外的过滤器,请通过 logback-dev 邮件列表进行联系。
536 |
537 | ## CountingFilter
538 |
539 | 在 [`CountingFilter`](https://logback.qos.ch/manual/xref/ch/qos/logback/access/filter/CountingFilter.html) 类的帮助下,logback-access 可以提供对服务器访问数据的统计。在初始化的死后,`CountingFilter` 将自己作为一个 MBean 注册到平台的 JMX 服务上。你可以通过轮询 MBean 来进行数据统计。例如,平均每分钟,每小时,每天,每周,或者每月。其它的统计,例如周计,天计,小时计,月计或者总计也是可以获取的。
540 |
541 | 下面的 *logback-access.xml* 配置文件声明了一个 `CountingFilter`。
542 |
543 | ```xml
544 |
545 |
546 |
547 |
548 | countingFilter
549 |
550 |
551 |
552 |
553 | %h %l %u %t \"%r\" %s %b
554 |
555 |
556 |
557 |
558 |
559 | ```
560 |
561 | 你可以通过 `jconsole` 查看有 `CountingFilte` 在你平台的 JMX 服务上维护的各种统计信息。
562 |
563 | 
564 |
565 | ### EvaluatorFilter
566 |
567 | [`EvaluatorFilter`](https://logback.qos.ch/xref/ch/qos/logback/core/filter/EvaluatorFilter.html) 是一个通用的过滤器,维护了一个 `EventEvaluator`。顾名思义,[`EventEvaluator`](https://logback.qos.ch/xref/ch/qos/logback/core/boolex/EventEvaluator.html) 根据给定的标准判断给定的日志事件是否满足,`EvaluatorFilter` 将会根据 match 与 mismatch 的情况,返回由 `onMatch` 或 `onMismatch` 属性指定的值。`EvaluatorFilter` 在之前的 logback-classic 中已经讨论过了 ([见上面](https://github.com/Volong/logback-chinese-manual/blob/master/07%E7%AC%AC%E4%B8%83%E7%AB%A0%EF%BC%9AFilters.md#evaluatorfilter))。现在大部分都是对之前讨论的重复。
568 |
569 | 注意 `EventEvaluator` 是一个抽象类。你可以通过继承 `EventEvaluator` 来实现你自己的评估逻辑。logback-access 附带了一个名为 [JaninoEventEvaluator](https://logback.qos.ch/xref/ch/qos/logback/access/boolex/JaninoEventEvaluator.html) 的具体实现。它可以接收任意的 Java 表达式作为评估标准。我们把这种 Java 代码块称为 "*评估表达式*"。评估表达式在事件过滤中有较大的灵活性。`JaninoEventEvaluator` 需要 [Janino 类库](http://docs.codehaus.org/display/JANINO/Home)。请查看[相应的文档](https://logback.qos.ch/setup.html#janino)进行设置。
570 |
571 | 评估表达式在解析配置文件的过程中被动态编译。作为用户,你不需要知道实际的细节。但是,你需要保证 Java 表达式返回一个布尔值,能够计算为 true 或者 false。
572 |
573 | 评估表达式可以对当前访问的事件进行评估。logback-access 会自动导出当前 `AccessEvent` 实例到变量 **event** 下。你可以通过 `event` 变量读取 HTTP 请求中以及 HTTP 响应中的各种数据。查看 [AccessEvent 类的源码](https://logback.qos.ch/xref/ch/qos/logback/access/spi/AccessEvent.html)来查看具体的列表。
574 |
575 | 下个配置文件基于 HTTP 响应码 [404 (Not Found)](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5) 来进行过滤。每一个 404 的请求都会在控制台打印出来。
576 |
577 | > Example: *accessEventEvaluator.xml*
578 |
579 | ```xml
580 |
581 |
582 |
583 |
584 |
585 |
586 | event.getStatusCode() == 404
587 |
588 | DENY
589 |
590 | %h %l %u %t %r %s %b
591 |
592 |
593 |
594 |
595 | ```
596 |
597 | 下面的例子,打印 404 错误,但是排除了请求 CSS 文件的请求。
598 |
599 | > Example: *accessEventEvaluator2.xml*
600 |
601 | ```xml
602 |
603 |
604 |
605 |
606 |
607 |
608 | (event.getStatusCode() == 404)
609 | &&
610 | !(event.getRequestURI().contains(".css"))
611 |
612 |
613 | DENY
614 |
615 |
616 | %h %l %u %t %r %s %b
617 |
618 |
619 |
620 |
621 | ```
622 |
623 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 2.1, February 1999
3 |
4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc.
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | [This is the first released version of the Lesser GPL. It also counts
10 | as the successor of the GNU Library Public License, version 2, hence
11 | the version number 2.1.]
12 |
13 | Preamble
14 |
15 | The licenses for most software are designed to take away your
16 | freedom to share and change it. By contrast, the GNU General Public
17 | Licenses are intended to guarantee your freedom to share and change
18 | free software--to make sure the software is free for all its users.
19 |
20 | This license, the Lesser General Public License, applies to some
21 | specially designated software packages--typically libraries--of the
22 | Free Software Foundation and other authors who decide to use it. You
23 | can use it too, but we suggest you first think carefully about whether
24 | this license or the ordinary General Public License is the better
25 | strategy to use in any particular case, based on the explanations below.
26 |
27 | When we speak of free software, we are referring to freedom of use,
28 | not price. Our General Public Licenses are designed to make sure that
29 | you have the freedom to distribute copies of free software (and charge
30 | for this service if you wish); that you receive source code or can get
31 | it if you want it; that you can change the software and use pieces of
32 | it in new free programs; and that you are informed that you can do
33 | these things.
34 |
35 | To protect your rights, we need to make restrictions that forbid
36 | distributors to deny you these rights or to ask you to surrender these
37 | rights. These restrictions translate to certain responsibilities for
38 | you if you distribute copies of the library or if you modify it.
39 |
40 | For example, if you distribute copies of the library, whether gratis
41 | or for a fee, you must give the recipients all the rights that we gave
42 | you. You must make sure that they, too, receive or can get the source
43 | code. If you link other code with the library, you must provide
44 | complete object files to the recipients, so that they can relink them
45 | with the library after making changes to the library and recompiling
46 | it. And you must show them these terms so they know their rights.
47 |
48 | We protect your rights with a two-step method: (1) we copyright the
49 | library, and (2) we offer you this license, which gives you legal
50 | permission to copy, distribute and/or modify the library.
51 |
52 | To protect each distributor, we want to make it very clear that
53 | there is no warranty for the free library. Also, if the library is
54 | modified by someone else and passed on, the recipients should know
55 | that what they have is not the original version, so that the original
56 | author's reputation will not be affected by problems that might be
57 | introduced by others.
58 |
59 | Finally, software patents pose a constant threat to the existence of
60 | any free program. We wish to make sure that a company cannot
61 | effectively restrict the users of a free program by obtaining a
62 | restrictive license from a patent holder. Therefore, we insist that
63 | any patent license obtained for a version of the library must be
64 | consistent with the full freedom of use specified in this license.
65 |
66 | Most GNU software, including some libraries, is covered by the
67 | ordinary GNU General Public License. This license, the GNU Lesser
68 | General Public License, applies to certain designated libraries, and
69 | is quite different from the ordinary General Public License. We use
70 | this license for certain libraries in order to permit linking those
71 | libraries into non-free programs.
72 |
73 | When a program is linked with a library, whether statically or using
74 | a shared library, the combination of the two is legally speaking a
75 | combined work, a derivative of the original library. The ordinary
76 | General Public License therefore permits such linking only if the
77 | entire combination fits its criteria of freedom. The Lesser General
78 | Public License permits more lax criteria for linking other code with
79 | the library.
80 |
81 | We call this license the "Lesser" General Public License because it
82 | does Less to protect the user's freedom than the ordinary General
83 | Public License. It also provides other free software developers Less
84 | of an advantage over competing non-free programs. These disadvantages
85 | are the reason we use the ordinary General Public License for many
86 | libraries. However, the Lesser license provides advantages in certain
87 | special circumstances.
88 |
89 | For example, on rare occasions, there may be a special need to
90 | encourage the widest possible use of a certain library, so that it becomes
91 | a de-facto standard. To achieve this, non-free programs must be
92 | allowed to use the library. A more frequent case is that a free
93 | library does the same job as widely used non-free libraries. In this
94 | case, there is little to gain by limiting the free library to free
95 | software only, so we use the Lesser General Public License.
96 |
97 | In other cases, permission to use a particular library in non-free
98 | programs enables a greater number of people to use a large body of
99 | free software. For example, permission to use the GNU C Library in
100 | non-free programs enables many more people to use the whole GNU
101 | operating system, as well as its variant, the GNU/Linux operating
102 | system.
103 |
104 | Although the Lesser General Public License is Less protective of the
105 | users' freedom, it does ensure that the user of a program that is
106 | linked with the Library has the freedom and the wherewithal to run
107 | that program using a modified version of the Library.
108 |
109 | The precise terms and conditions for copying, distribution and
110 | modification follow. Pay close attention to the difference between a
111 | "work based on the library" and a "work that uses the library". The
112 | former contains code derived from the library, whereas the latter must
113 | be combined with the library in order to run.
114 |
115 | GNU LESSER GENERAL PUBLIC LICENSE
116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
117 |
118 | 0. This License Agreement applies to any software library or other
119 | program which contains a notice placed by the copyright holder or
120 | other authorized party saying it may be distributed under the terms of
121 | this Lesser General Public License (also called "this License").
122 | Each licensee is addressed as "you".
123 |
124 | A "library" means a collection of software functions and/or data
125 | prepared so as to be conveniently linked with application programs
126 | (which use some of those functions and data) to form executables.
127 |
128 | The "Library", below, refers to any such software library or work
129 | which has been distributed under these terms. A "work based on the
130 | Library" means either the Library or any derivative work under
131 | copyright law: that is to say, a work containing the Library or a
132 | portion of it, either verbatim or with modifications and/or translated
133 | straightforwardly into another language. (Hereinafter, translation is
134 | included without limitation in the term "modification".)
135 |
136 | "Source code" for a work means the preferred form of the work for
137 | making modifications to it. For a library, complete source code means
138 | all the source code for all modules it contains, plus any associated
139 | interface definition files, plus the scripts used to control compilation
140 | and installation of the library.
141 |
142 | Activities other than copying, distribution and modification are not
143 | covered by this License; they are outside its scope. The act of
144 | running a program using the Library is not restricted, and output from
145 | such a program is covered only if its contents constitute a work based
146 | on the Library (independent of the use of the Library in a tool for
147 | writing it). Whether that is true depends on what the Library does
148 | and what the program that uses the Library does.
149 |
150 | 1. You may copy and distribute verbatim copies of the Library's
151 | complete source code as you receive it, in any medium, provided that
152 | you conspicuously and appropriately publish on each copy an
153 | appropriate copyright notice and disclaimer of warranty; keep intact
154 | all the notices that refer to this License and to the absence of any
155 | warranty; and distribute a copy of this License along with the
156 | Library.
157 |
158 | You may charge a fee for the physical act of transferring a copy,
159 | and you may at your option offer warranty protection in exchange for a
160 | fee.
161 |
162 | 2. You may modify your copy or copies of the Library or any portion
163 | of it, thus forming a work based on the Library, and copy and
164 | distribute such modifications or work under the terms of Section 1
165 | above, provided that you also meet all of these conditions:
166 |
167 | a) The modified work must itself be a software library.
168 |
169 | b) You must cause the files modified to carry prominent notices
170 | stating that you changed the files and the date of any change.
171 |
172 | c) You must cause the whole of the work to be licensed at no
173 | charge to all third parties under the terms of this License.
174 |
175 | d) If a facility in the modified Library refers to a function or a
176 | table of data to be supplied by an application program that uses
177 | the facility, other than as an argument passed when the facility
178 | is invoked, then you must make a good faith effort to ensure that,
179 | in the event an application does not supply such function or
180 | table, the facility still operates, and performs whatever part of
181 | its purpose remains meaningful.
182 |
183 | (For example, a function in a library to compute square roots has
184 | a purpose that is entirely well-defined independent of the
185 | application. Therefore, Subsection 2d requires that any
186 | application-supplied function or table used by this function must
187 | be optional: if the application does not supply it, the square
188 | root function must still compute square roots.)
189 |
190 | These requirements apply to the modified work as a whole. If
191 | identifiable sections of that work are not derived from the Library,
192 | and can be reasonably considered independent and separate works in
193 | themselves, then this License, and its terms, do not apply to those
194 | sections when you distribute them as separate works. But when you
195 | distribute the same sections as part of a whole which is a work based
196 | on the Library, the distribution of the whole must be on the terms of
197 | this License, whose permissions for other licensees extend to the
198 | entire whole, and thus to each and every part regardless of who wrote
199 | it.
200 |
201 | Thus, it is not the intent of this section to claim rights or contest
202 | your rights to work written entirely by you; rather, the intent is to
203 | exercise the right to control the distribution of derivative or
204 | collective works based on the Library.
205 |
206 | In addition, mere aggregation of another work not based on the Library
207 | with the Library (or with a work based on the Library) on a volume of
208 | a storage or distribution medium does not bring the other work under
209 | the scope of this License.
210 |
211 | 3. You may opt to apply the terms of the ordinary GNU General Public
212 | License instead of this License to a given copy of the Library. To do
213 | this, you must alter all the notices that refer to this License, so
214 | that they refer to the ordinary GNU General Public License, version 2,
215 | instead of to this License. (If a newer version than version 2 of the
216 | ordinary GNU General Public License has appeared, then you can specify
217 | that version instead if you wish.) Do not make any other change in
218 | these notices.
219 |
220 | Once this change is made in a given copy, it is irreversible for
221 | that copy, so the ordinary GNU General Public License applies to all
222 | subsequent copies and derivative works made from that copy.
223 |
224 | This option is useful when you wish to copy part of the code of
225 | the Library into a program that is not a library.
226 |
227 | 4. You may copy and distribute the Library (or a portion or
228 | derivative of it, under Section 2) in object code or executable form
229 | under the terms of Sections 1 and 2 above provided that you accompany
230 | it with the complete corresponding machine-readable source code, which
231 | must be distributed under the terms of Sections 1 and 2 above on a
232 | medium customarily used for software interchange.
233 |
234 | If distribution of object code is made by offering access to copy
235 | from a designated place, then offering equivalent access to copy the
236 | source code from the same place satisfies the requirement to
237 | distribute the source code, even though third parties are not
238 | compelled to copy the source along with the object code.
239 |
240 | 5. A program that contains no derivative of any portion of the
241 | Library, but is designed to work with the Library by being compiled or
242 | linked with it, is called a "work that uses the Library". Such a
243 | work, in isolation, is not a derivative work of the Library, and
244 | therefore falls outside the scope of this License.
245 |
246 | However, linking a "work that uses the Library" with the Library
247 | creates an executable that is a derivative of the Library (because it
248 | contains portions of the Library), rather than a "work that uses the
249 | library". The executable is therefore covered by this License.
250 | Section 6 states terms for distribution of such executables.
251 |
252 | When a "work that uses the Library" uses material from a header file
253 | that is part of the Library, the object code for the work may be a
254 | derivative work of the Library even though the source code is not.
255 | Whether this is true is especially significant if the work can be
256 | linked without the Library, or if the work is itself a library. The
257 | threshold for this to be true is not precisely defined by law.
258 |
259 | If such an object file uses only numerical parameters, data
260 | structure layouts and accessors, and small macros and small inline
261 | functions (ten lines or less in length), then the use of the object
262 | file is unrestricted, regardless of whether it is legally a derivative
263 | work. (Executables containing this object code plus portions of the
264 | Library will still fall under Section 6.)
265 |
266 | Otherwise, if the work is a derivative of the Library, you may
267 | distribute the object code for the work under the terms of Section 6.
268 | Any executables containing that work also fall under Section 6,
269 | whether or not they are linked directly with the Library itself.
270 |
271 | 6. As an exception to the Sections above, you may also combine or
272 | link a "work that uses the Library" with the Library to produce a
273 | work containing portions of the Library, and distribute that work
274 | under terms of your choice, provided that the terms permit
275 | modification of the work for the customer's own use and reverse
276 | engineering for debugging such modifications.
277 |
278 | You must give prominent notice with each copy of the work that the
279 | Library is used in it and that the Library and its use are covered by
280 | this License. You must supply a copy of this License. If the work
281 | during execution displays copyright notices, you must include the
282 | copyright notice for the Library among them, as well as a reference
283 | directing the user to the copy of this License. Also, you must do one
284 | of these things:
285 |
286 | a) Accompany the work with the complete corresponding
287 | machine-readable source code for the Library including whatever
288 | changes were used in the work (which must be distributed under
289 | Sections 1 and 2 above); and, if the work is an executable linked
290 | with the Library, with the complete machine-readable "work that
291 | uses the Library", as object code and/or source code, so that the
292 | user can modify the Library and then relink to produce a modified
293 | executable containing the modified Library. (It is understood
294 | that the user who changes the contents of definitions files in the
295 | Library will not necessarily be able to recompile the application
296 | to use the modified definitions.)
297 |
298 | b) Use a suitable shared library mechanism for linking with the
299 | Library. A suitable mechanism is one that (1) uses at run time a
300 | copy of the library already present on the user's computer system,
301 | rather than copying library functions into the executable, and (2)
302 | will operate properly with a modified version of the library, if
303 | the user installs one, as long as the modified version is
304 | interface-compatible with the version that the work was made with.
305 |
306 | c) Accompany the work with a written offer, valid for at
307 | least three years, to give the same user the materials
308 | specified in Subsection 6a, above, for a charge no more
309 | than the cost of performing this distribution.
310 |
311 | d) If distribution of the work is made by offering access to copy
312 | from a designated place, offer equivalent access to copy the above
313 | specified materials from the same place.
314 |
315 | e) Verify that the user has already received a copy of these
316 | materials or that you have already sent this user a copy.
317 |
318 | For an executable, the required form of the "work that uses the
319 | Library" must include any data and utility programs needed for
320 | reproducing the executable from it. However, as a special exception,
321 | the materials to be distributed need not include anything that is
322 | normally distributed (in either source or binary form) with the major
323 | components (compiler, kernel, and so on) of the operating system on
324 | which the executable runs, unless that component itself accompanies
325 | the executable.
326 |
327 | It may happen that this requirement contradicts the license
328 | restrictions of other proprietary libraries that do not normally
329 | accompany the operating system. Such a contradiction means you cannot
330 | use both them and the Library together in an executable that you
331 | distribute.
332 |
333 | 7. You may place library facilities that are a work based on the
334 | Library side-by-side in a single library together with other library
335 | facilities not covered by this License, and distribute such a combined
336 | library, provided that the separate distribution of the work based on
337 | the Library and of the other library facilities is otherwise
338 | permitted, and provided that you do these two things:
339 |
340 | a) Accompany the combined library with a copy of the same work
341 | based on the Library, uncombined with any other library
342 | facilities. This must be distributed under the terms of the
343 | Sections above.
344 |
345 | b) Give prominent notice with the combined library of the fact
346 | that part of it is a work based on the Library, and explaining
347 | where to find the accompanying uncombined form of the same work.
348 |
349 | 8. You may not copy, modify, sublicense, link with, or distribute
350 | the Library except as expressly provided under this License. Any
351 | attempt otherwise to copy, modify, sublicense, link with, or
352 | distribute the Library is void, and will automatically terminate your
353 | rights under this License. However, parties who have received copies,
354 | or rights, from you under this License will not have their licenses
355 | terminated so long as such parties remain in full compliance.
356 |
357 | 9. You are not required to accept this License, since you have not
358 | signed it. However, nothing else grants you permission to modify or
359 | distribute the Library or its derivative works. These actions are
360 | prohibited by law if you do not accept this License. Therefore, by
361 | modifying or distributing the Library (or any work based on the
362 | Library), you indicate your acceptance of this License to do so, and
363 | all its terms and conditions for copying, distributing or modifying
364 | the Library or works based on it.
365 |
366 | 10. Each time you redistribute the Library (or any work based on the
367 | Library), the recipient automatically receives a license from the
368 | original licensor to copy, distribute, link with or modify the Library
369 | subject to these terms and conditions. You may not impose any further
370 | restrictions on the recipients' exercise of the rights granted herein.
371 | You are not responsible for enforcing compliance by third parties with
372 | this License.
373 |
374 | 11. If, as a consequence of a court judgment or allegation of patent
375 | infringement or for any other reason (not limited to patent issues),
376 | conditions are imposed on you (whether by court order, agreement or
377 | otherwise) that contradict the conditions of this License, they do not
378 | excuse you from the conditions of this License. If you cannot
379 | distribute so as to satisfy simultaneously your obligations under this
380 | License and any other pertinent obligations, then as a consequence you
381 | may not distribute the Library at all. For example, if a patent
382 | license would not permit royalty-free redistribution of the Library by
383 | all those who receive copies directly or indirectly through you, then
384 | the only way you could satisfy both it and this License would be to
385 | refrain entirely from distribution of the Library.
386 |
387 | If any portion of this section is held invalid or unenforceable under any
388 | particular circumstance, the balance of the section is intended to apply,
389 | and the section as a whole is intended to apply in other circumstances.
390 |
391 | It is not the purpose of this section to induce you to infringe any
392 | patents or other property right claims or to contest validity of any
393 | such claims; this section has the sole purpose of protecting the
394 | integrity of the free software distribution system which is
395 | implemented by public license practices. Many people have made
396 | generous contributions to the wide range of software distributed
397 | through that system in reliance on consistent application of that
398 | system; it is up to the author/donor to decide if he or she is willing
399 | to distribute software through any other system and a licensee cannot
400 | impose that choice.
401 |
402 | This section is intended to make thoroughly clear what is believed to
403 | be a consequence of the rest of this License.
404 |
405 | 12. If the distribution and/or use of the Library is restricted in
406 | certain countries either by patents or by copyrighted interfaces, the
407 | original copyright holder who places the Library under this License may add
408 | an explicit geographical distribution limitation excluding those countries,
409 | so that distribution is permitted only in or among countries not thus
410 | excluded. In such case, this License incorporates the limitation as if
411 | written in the body of this License.
412 |
413 | 13. The Free Software Foundation may publish revised and/or new
414 | versions of the Lesser General Public License from time to time.
415 | Such new versions will be similar in spirit to the present version,
416 | but may differ in detail to address new problems or concerns.
417 |
418 | Each version is given a distinguishing version number. If the Library
419 | specifies a version number of this License which applies to it and
420 | "any later version", you have the option of following the terms and
421 | conditions either of that version or of any later version published by
422 | the Free Software Foundation. If the Library does not specify a
423 | license version number, you may choose any version ever published by
424 | the Free Software Foundation.
425 |
426 | 14. If you wish to incorporate parts of the Library into other free
427 | programs whose distribution conditions are incompatible with these,
428 | write to the author to ask for permission. For software which is
429 | copyrighted by the Free Software Foundation, write to the Free
430 | Software Foundation; we sometimes make exceptions for this. Our
431 | decision will be guided by the two goals of preserving the free status
432 | of all derivatives of our free software and of promoting the sharing
433 | and reuse of software generally.
434 |
435 | NO WARRANTY
436 |
437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
446 |
447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
456 | DAMAGES.
457 |
458 | END OF TERMS AND CONDITIONS
459 |
460 | How to Apply These Terms to Your New Libraries
461 |
462 | If you develop a new library, and you want it to be of the greatest
463 | possible use to the public, we recommend making it free software that
464 | everyone can redistribute and change. You can do so by permitting
465 | redistribution under these terms (or, alternatively, under the terms of the
466 | ordinary General Public License).
467 |
468 | To apply these terms, attach the following notices to the library. It is
469 | safest to attach them to the start of each source file to most effectively
470 | convey the exclusion of warranty; and each file should have at least the
471 | "copyright" line and a pointer to where the full notice is found.
472 |
473 |
474 | Copyright (C)
475 |
476 | This library is free software; you can redistribute it and/or
477 | modify it under the terms of the GNU Lesser General Public
478 | License as published by the Free Software Foundation; either
479 | version 2.1 of the License, or (at your option) any later version.
480 |
481 | This library is distributed in the hope that it will be useful,
482 | but WITHOUT ANY WARRANTY; without even the implied warranty of
483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
484 | Lesser General Public License for more details.
485 |
486 | You should have received a copy of the GNU Lesser General Public
487 | License along with this library; if not, write to the Free Software
488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
489 | USA
490 |
491 | Also add information on how to contact you by electronic and paper mail.
492 |
493 | You should also get your employer (if you work as a programmer) or your
494 | school, if any, to sign a "copyright disclaimer" for the library, if
495 | necessary. Here is a sample; alter the names:
496 |
497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the
498 | library `Frob' (a library for tweaking knobs) written by James Random
499 | Hacker.
500 |
501 | , 1 April 1990
502 | Ty Coon, President of Vice
503 |
504 | That's all there is to it!
505 |
--------------------------------------------------------------------------------