What is an encoder
编码器负责将事件转换为字节数组,并将该字节数组写入OutputStream。在logback版本0.9.19中引入了编码器。在以前的版本中,大多数追加器依赖于layout将事件转换为字符串,并使用java.io.Writer将其写出来。在以前的logback版本中,用户会在FileAppender中嵌套PatternLayout。从logback 0.9.19开始,FileAppender和子类期望一个编码器,而不再采用布局
为什么会出现这种突破性的变化?
正如下一章详细讨论的那样,布局只能将事件转换为String。此外,由于布局无法控制何时写出事件,所以布局无法将事件批量聚合。相比之下,编码器不仅可以完全控制输出的字节的格式,还可以控制何时(以及是否)输出这些字节。
Encoder interface
编码器负责将传入的事件转换为字节数组,并将结果字节数组写到适当的OutputStream上。因此,编码器可以完全控制哪些字节以及何时写入由所属appender维护的OutputStream。这里是编码器接口:
package ch.qos.logback.core.encoder;public interface Encoder<E> extends ContextAware, LifeCycle {/*** This method is called when the owning appender starts or whenever output* needs to be directed to a new OutputStream, for instance as a result of a* rollover.*/void init(OutputStream os) throws IOException;/*** Encode and write an event to the appropriate {@link OutputStream}。编码并写入一个事件到适当的{@link OutputStream}。* Implementations are free to defer writing out of the encoded event and instead write in batches. 实现可以自由地推迟对已编码事件的写入,还是批量写入。*/void doEncode(E event) throws IOException;/*** This method is called prior to the closing of the underling* {@link OutputStream}. Implementations MUST not close the underlying* {@link OutputStream} which is the responsibility of the owning appender.*/void close() throws IOException;}
如您所见,Encoder接口由少数方法组成,但令人惊讶的是,使用这些方法可以完成许多有用的事情。
LayoutWrappingEncoder
在logback版本0.9.19之前,许多appender依赖Layout实例来控制日志输出的格式。由于存在大量基于布局界面的代码,我们需要一种编码器与布局互操作的方法。layoutrappingencoder在编码器和布局之间架起了桥梁。它实现了编码器接口,并包装了一个布局,将事件转换为字符串的工作委托给该布局。
下面是layoutrappingencoder类的摘录,说明了如何委托包装的布局实例。
package ch.qos.logback.core.encoder;public class LayoutWrappingEncoder<E> extends EncoderBase<E> {protected Layout<E> layout;private Charset charset;// encode a given event as a byte[]public byte[] encode(E event) {String txt = layout.doLayout(event);return convertToBytes(txt);}private byte[] convertToBytes(String s) {if (charset == null) {return s.getBytes();} else {return s.getBytes(charset);}}}
doEncode()方法首先让包装的布局将传入的事件转换为字符串。根据用户选择的字符集编码将生成的文本字符串转换为字节。
PatternLayoutEncoder
考虑到PatternLayout是最常用的布局,logback通过PatternLayoutEncoder (layoutrappingencoder的扩展,仅限于包装PatternLayout的实例)来满足这个常用用例。
从logback版本0.9.19开始,每当FileAppender或它的一个子类被配置为PatternLayout时,必须使用PatternLayoutEncoder。这在logback错误码的相关条目中有解释。
immediateFlush property
从LOGBACK 1.2.0开始,immediateFlush属性是外围Appender的一部分。
Output pattern string as header
为了方便日志文件的解析,logback可以在日志文件的顶部插入用于日志输出的模式。该功能在默认情况下是禁用的。它可以通过设置相关的PatternLayoutEncoder的outputPatternAsHeader属性为’true’来启用。下面是一个例子:
<appender name="FILE" class="ch.qos.logback.core.FileAppender"><file>foo.log</file><encoder><pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern><outputPatternAsHeader>true</outputPatternAsHeader></encoder></appender>
这将在日志文件中输出类似如下内容:
#logback.classic pattern: %d [%thread] %-5level %logger{36} - %msg%n2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hello world2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hi again...
The line starting with “#logback.classic pattern” is newly inserted pattern line.
Chapter 6: Layouts
What is a layout?
如果你想知道,布局与佛罗里达州的大型房地产没有任何关系。布局是负责将传入事件转换为String的logback组件。Layout 接口中的format()方法接受一个表示事件(任何类型)的对象,并返回一个String。Layout接口的概要如下所示。
public interface Layout<E> extends ContextAware, LifeCycle {String doLayout(E event);String getFileHeader();String getPresentationHeader();String getFileFooter();String getPresentationFooter();String getContentType();}
这个接口相当简单,但足以满足许多格式需求。来自德克萨斯州的开发者(你可能从Joseph Heller的《第二十二条军规》中认识他)可能会感叹:实现一个布局只需要5种方法!!?
Logback-classic
Logback-classic仅处理ch.qos.logback.classic.spi.ILoggingEvent类型的事件。这一事实将贯穿本节。
Writing your own custom Layout
让我们为logback-classic模块实现一个简单但功能完备的布局,该模块打印自应用程序启动以来经过的时间、日志事件的级别、括号中调用者线程、日志记录器名称、后跟事件消息的破折号和新行。
Sample output might look like:
10489 DEBUG [main] com.marsupial.Pouch - Hello world.
这里是一个可能的实现,由德克萨斯开发人员编写:
Example: Sample implementation of a Layout
package chapters.layouts;import ch.qos.logback.classic.spi.ILoggingEvent;import ch.qos.logback.core.LayoutBase;public class MySampleLayout extends LayoutBase<ILoggingEvent> {public String doLayout(ILoggingEvent event) {StringBuffer sbuf = new StringBuffer(128);sbuf.append(event.getTimeStamp() - event.getLoggingContextVO.getBirthTime());sbuf.append(" ");sbuf.append(event.getLevel());sbuf.append(" [");sbuf.append(event.getThreadName());sbuf.append("] ");sbuf.append(event.getLoggerName();sbuf.append(" - ");sbuf.append(event.getFormattedMessage());sbuf.append(CoreConstants.LINE_SEP);return sbuf.toString();}}
注意MySampleLayout扩展了LayoutBase。这个类管理所有布局实例的公共状态,例如布局是启动还是停止、页眉、页脚和内容类型数据。它允许开发人员专注于他/她的布局预期的格式。注意,LayoutBase类是通用的。在它的类声明中,MySampleLayout扩展了LayoutBase
doLayout(ILoggingEvent event)方法,也就是MySampleLayout中唯一的方法,首先实例化一个StringBuffer。它接着添加事件参数的各种字段。来自德克萨斯的德克萨斯人小心翼翼地打印了信息的格式。如果一个或多个参数随日志请求一起传递,则这一点非常重要。
将这些不同的字符添加到字符串缓冲区后,doLayout()方法将缓冲区转换为string并返回结果值。
在上面的示例中,doLayout方法忽略事件中包含的任何最终异常。在实际的布局实现中,您很可能也想打印异常的内容。
Configuring your custom layout
自定义布局被配置为任何其他组件。如前所述,FileAppender及其子类需要一个编码器。为了满足这个要求,我们传递给FileAppender一个layoutrappingencoder的实例,它包装了我们的MySampleLayout。下面是配置文件:
Example: Configuration of MySampleLayout
<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"><layout class="chapters.layouts.MySampleLayout" /></encoder></appender><root level="DEBUG"><appender-ref ref="STDOUT" /></root></configuration>
样例应用程序chapter .layouts. sample logging配置logback,将配置脚本作为其第一个参数传递,然后记录一条调试消息,后面是一条错误消息。
要运行此示例,请在logback-examples目录中发出以下命令。
java chapters.layouts.SampleLogging src/main/java/chapters/layouts/sampleLayoutConfig.xml
This will produce:
0 DEBUG [main] chapters.layouts.SampleLogging - Everything's going well0 ERROR [main] chapters.layouts.SampleLogging - maybe not quite...
有选项的布局怎么样? 读者可以在MySampleLayout2.java中找到一个稍微修改过的自定义布局。正如本手册中所提到的,向布局或任何其他logback组件添加属性就像为属性声明setter方法一样简单。
MySampleLayout2类包含两个属性。第一个是可以添加到输出的前缀。第二个属性用于选择是否显示发送日志请求的线程的名称。
Here is a copy of the MySampleLayout2 class :
package chapters.layouts;import ch.qos.logback.classic.spi.ILoggingEvent;import ch.qos.logback.core.LayoutBase;public class MySampleLayout2 extends LayoutBase<ILoggingEvent> {String prefix = null;boolean printThreadName = true;public void setPrefix(String prefix) {this.prefix = prefix;}public void setPrintThreadName(boolean printThreadName) {this.printThreadName = printThreadName;}public String doLayout(ILoggingEvent event) {StringBuffer sbuf = new StringBuffer(128);if (prefix != null) {sbuf.append(prefix + ": ");}sbuf.append(event.getTimeStamp() - event.getLoggerContextVO().getBirthTime());sbuf.append(" ");sbuf.append(event.getLevel());if (printThreadName) {sbuf.append(" [");sbuf.append(event.getThreadName());sbuf.append("] ");} else {sbuf.append(" ");}sbuf.append(event.getLoggerName());sbuf.append(" - ");sbuf.append(event.getFormattedMessage());sbuf.append(LINE_SEP);return sbuf.toString();}}
要启用属性的配置,只需要添加相应的setter方法。注意,PrintThreadName属性是一个布尔值而不是字符串。logback组件的配置在配置一章中有详细介绍。关于Joran的那一章提供了更多细节。下面是为MySampleLayout2定制的配置文件。
<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"><layout class="chapters.layouts.MySampleLayout2"><prefix>MyPrefix</prefix><printThreadName>false</printThreadName></layout></encoder></appender><root level="DEBUG"><appender-ref ref="STDOUT" /></root></configuration>
PatternLayout
Logback classic 提供一个灵活的布局称为PatternLayout。和所有布局一样,PatternLayout接受一个日志事件并返回一个String。然而,这个字符串可以通过调整PatternLayout的转换模式来定制。
PatternLayout的转换模式与C编程语言中printf()函数的转换模式密切相关。转换模式由称为转换说明符的文字文本和格式控制表达式组成。您可以自由地在转换模式中插入任何文本。
每个转换说明符以百分号’%’开始,后面跟着可选的格式修饰符、转换字和大括号之间的可选参数。转换字控制要转换的数据字段,例如记录器名称、级别、日期或线程名称。格式修饰符控制字段的宽度、填充和左右对齐。
正如已经多次提到的,FileAppender和子类需要一个编码器。因此,当与FileAppender或其子类一起使用时,PatternLayout必须包装在编码器中。考虑到FileAppender/PatternLayout的组合是如此常见,logback附带了一个名为PatternLayoutEncoder的编码器,设计它的唯一目的是包装一个PatternLayout实例,以便它可以被视为编码器。下面是一个使用PatternLayoutEncoder编程配置ConsoleAppender的例子:
Example: Sample usage of a PatternLayout
package chapters.layouts;import org.slf4j.LoggerFactory;import ch.qos.logback.classic.Logger;import ch.qos.logback.classic.LoggerContext;import ch.qos.logback.classic.encoder.PatternLayoutEncoder;import ch.qos.logback.classic.spi.ILoggingEvent;import ch.qos.logback.core.ConsoleAppender;public class PatternSample {static public void main(String[] args) throws Exception {Logger rootLogger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);LoggerContext loggerContext = rootLogger.getLoggerContext();// we are not interested in auto-configurationloggerContext.reset();PatternLayoutEncoder encoder = new PatternLayoutEncoder();encoder.setContext(loggerContext);encoder.setPattern("%-5level [%thread]: %message%n");encoder.start();ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();appender.setContext(loggerContext);appender.setEncoder(encoder);appender.start();rootLogger.addAppender(appender);rootLogger.debug("Message 1");rootLogger.warn("Message 2");}}
在上面的例子中,转换模式被设置为“%-5level [%thread]: %message%n”。稍后将给出包含在logback中的转换词的概要。运行PatternSample应用程序如下:
java java chapters.layouts.PatternSample
将在控制台上产生以下输出。
DEBUG [main]: Message 1WARN [main]: Message 2
请注意,在转换模式“%-5level [%thread]: %message%n”中,在文本文本和转换说明符之间没有显式的分隔符。在解析转换模式时,PatternLayout能够区分字面文本(空格字符、括号、冒号字符)和转换说明符。在上面的例子中,转换说明符%-5level表示日志记录事件的级别应该保持为5个字符的宽度。格式说明符将在下面解释。
在PatternLayout中,括号可以用来对转换模式进行分组。因此,’(‘和’)’具有特殊的含义,如果打算作为字面量使用,需要转义。下面将进一步说明括号的特殊性质。
如前所述,某些转换说明符可能包括在花括号之间传递的可选参数。带有选项的示例转换说明符可以是%logger{10}。这里“logger”是转换词,10是选项。下面将进一步讨论选项。
下表描述了已识别的转换词及其选项。当在同一个表格单元格中列出多个转换词时,将它们视为别名。
c{length}, lo{length}, logger{length}
在日志记录事件的起始处输出记录器的名称。
这个转换字接受一个整数作为它的第一个也是唯一的选项。转换器的缩写算法将缩短记录器名称,通常不会有重大的意义损失。将length选项的值设置为零将构成异常。它将导致转换字将子字符串返回到记录器名称中最右边的点字符。下表提供了缩略语算法的示例。
请注意,记录器名称中最右边的段永远不会缩写,即使它的长度比length选项长。其他段最多可以缩短为一个字符,但永远不会删除。
c{length}, class{length}
输出发出日志记录请求的调用者的完全限定类名。
与上面的%logger转换字一样,此转换采用一个整数作为选项来缩短类名。0具有特殊的含义,并将导致在没有包名前缀的情况下打印简单的类名。默认情况下,类名是完整打印的。
生成调用者类信息不是特别快。因此,除非执行速度不是问题,否则应该避免使用它。
contextNamecn**
输出事件源头的记录器附加到的记录器上下文的名称。
d{pattern}
date{pattern}
d{pattern, timezone}
date{pattern, timezone}
用于输出日志记录事件的日期。日期转换字允许模式字符串作为参数。模式语法与java.text.SimpleDateFormat接受的格式兼容。
您可以为ISO8601日期格式指定字符串“ISO8601”。请注意,在没有模式参数的情况下,%date转换字默认为ISO 8601日期格式。
这里有一些参数值的示例。他们假设实际日期是2006年10月20日星期五,并且作者在午饭后就返回工作。
%d 2006-10-20 14:06:49,812
%date 2006-10-20 14:06:49,812
%date{ISO8601} 2006-10-20 14:06:49,812
%date{HH:mm:ss.SSS} 14:06:49.812
%date{dd MMM yyyy;HH:mm:ss.SSS} 20 oct. 2006;14:06:49.812
第二个参数指定时区。例如,’%date{HH:mm:ss ‘。SSS, Australia/Perth}将打印澳大利亚珀斯时区的时间,珀斯是世界上最孤立的城市。注意,在没有timezone参数的情况下,将使用主机Java平台的默认时区。如果指定的时区标识符未知或拼写错误,则假定GMT时区是由timezone . gettimezone (String)方法规范指定的。
假设逗号’,’字符被解释为参数分隔符,模式HH:mm:ss,SSS将被解释为模式HM:mm:ss和时区SSS。如果您希望在日期模式中包含逗号,只需将该模式括在引号之间。例如, %date{“HH:mm:ss,SSS“}
F / file
输出发出日志请求的Java源文件的文件名。
生成文件信息的速度不是特别快。因此,除非执行速度不是问题,否则应该避免使用它。
caller{depth}
caller{depthStart..depthEnd}
caller{depth, evaluator-1, … evaluator-n}
caller{depthStart..depthEnd, evaluator-1, … evaluator-n}
输出生成日志事件的调用者的位置信息。
位置信息取决于JVM实现,但通常由调用方法的完全限定名、调用方的源、文件名和括号之间的行号组成。
可以将整数添加到调用者转换说明符的选项中,以配置要显示的信息的深度。
For example, %caller{2} would display the following excerpt:
0 [main] DEBUG - logging statementCaller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22)Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17)
And %caller{3} would display this other excerpt:
16 [main] DEBUG - logging statementCaller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22)Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17)Caller+2 at mainPackage.ConfigTester.main(ConfigTester.java:38)
可以将范围说明符添加到调用者转换说明符的选项中,以配置要显示的信息的深度范围。
For example, %caller{1..2} would display the following excerpt:
0 [main] DEBUG - logging statementCaller+0 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17)
这个转换字还可以使用评估器在计算调用者数据之前,根据给定的标准测试日志事件。例如,使用%caller{3, CALLER_DISPLAY_EVAL}将显示三行stacktrace,只有当调用CALLER_DISPLAY_EVAL的求值器返回一个true的答案时。
% character has special meaning
在转换模式的上下文中,百分号具有特殊的含义,为了将其作为文字包含,需要使用反斜杠进行转义,例如:“%d %p %m%n”。
对紧接在转换词之后的文字的限制
在大多数情况下,字面量自然包含空格或其他定界字符,以便它们不会与转换词混淆。例如,模式“%level [%thread] - %message%n”包含字符串字面量“[”和“]-”。但是,如果一个可以作为java标识符一部分的字符紧跟在一个转换字之后,logback的模式解析器就会误以为该文字是转换字的一部分。例如,模式“%date%nHello”将被解释为两个转换词%date和%nHello,由于%nHello不是一个已知的转换词,logback将为%nHello输出%PARSER_ERROR[nHello]。如果您希望字符串字面量“Hello”立即分隔%n和Hello,请向%n传递一个空参数列表。例如,“%date%n{}Hello”将被解释为%date后跟%n,后跟文字“Hello”。
Format modifiers
默认情况下,相关信息按原样输出。然而,在格式修饰符的帮助下,可以更改每个数据字段的最小和最大宽度以及合理性。
可选的格式修饰符放在百分号和转换字符或单词之间。
第一个可选的格式修饰符是左对齐标志,即负号(-)字符。然后是可选的最小字段宽度修饰符。这是一个十进制常量,表示要输出的最小字符数。如果数据项包含的字符较少,则会在左侧或右侧填充,直到达到最小宽度。默认情况下是填充左侧(右对齐),但您可以使用左对齐标志指定右填充。填充字符是空格。如果数据项大于最小字段宽度,则将展开字段以容纳数据。这个值永远不会被截断。
可以使用最大字段宽度修饰符更改此行为,该修饰符由句点后跟十进制常量指定。如果数据项比最大字段长,则从数据项的开头删除多余的字符。例如,如果字段的最大宽度是8,而数据项的长度是10个字符,那么数据项的前两个字符将被删除。这种行为与C中的printf函数不同,在后者中,截断是从末尾开始的。
从末尾截取可以在句号之后加上一个减号字符。在这种情况下,如果字段的最大宽度是8,数据项的长度是10个字符,那么数据项的最后两个字符将被删除。
下面是记录器转换说明符的各种格式修饰符示例。
| Format modifier | Left justify | Minimum width | Maximum width | Comment |
|---|---|---|---|---|
| %20logger | false | 20 | none | Left pad with spaces if the logger name is less than 20 characters long. |
| %-20logger | true | 20 | none | Right pad with spaces if the logger name is less than 20 characters long. |
| %.30logger | NA | none | 30 | 如果记录器名称超过30个字符,则从开头截断。 |
| %20.30logger | false | 20 | 30 | 如果记录器名称小于20个字符,则用空格填充左边。但是,如果记录器名称超过30个字符,则从开始截断。 |
| %-20.30logger | true | 20 | 30 | 如果记录器名称小于20个字符,则用空格向右填充。但是,如果记录器名称超过30个字符,则从开始截断。 |
| %.-30logger | NA | none | 30 | 如果记录器名称超过30个字符,则从末尾截断。 |
下表列出了格式修饰符截断的示例。请注意,方括号,即“[]”字符对,不是输出的一部分。它们用于分隔输出的宽度。
| Format modifier | Logger name | Result |
|---|---|---|
| [%20.20logger] | main.Name | [ main.Name] |
| [%-20.20logger] | main.Name | [main.Name ] |
| [%10.10logger] | main.foo.foo.bar.Name | [o.bar.Name] |
| [%10.-10logger] | main.foo.foo.bar.Name | [main.foo.f] |
Output just one letter for the level
Instead of printing TRACE, DEBUG, WARN, INFO or ERROR for the level, you may want to print just T, D, W, I and E. You could write a custom converter for this purpose, or simply make use of format modifiers (just discussed) to shorten the level value to a single character. The appropriate conversion specifier would be “%.-1level”.
Conversion word options
转换说明符后面可以跟着选项。总是在大括号之间声明。我们已经看到了选项提供的一些可能性,例如与MDC转换说明符结合使用,如:% MDC {someKey}。
转换说明符可能有多个选项。例如,使用求值器的转换说明符(稍后将介绍)可能会将求值器名称添加到选项列表中,如下所示:
<pattern>%-4relative [%thread] %-5level - %msg%n \%caller{2, DISP_CALLER_EVAL, OTHER_EVAL_NAME, THIRD_EVAL_NAME}</pattern>
如果该选项包含大括号、空格或逗号等特殊字符,则可以将其括在单引号或双引号之间。例如,考虑下一个模式。
<pattern>%-5level - %replace(%msg){'\d{14,16}', 'XXXX'}%n</pattern>
我们将选项\d{16}和XXXX传递给替换转换字。它将消息中包含的任何14、15或16位数字序列替换为XXXX,有效地混淆了信用卡号码。注意,”\d”是正则表达式中单个数字的简写。“{14,16}”被解释为“{14,16}”,即重复前一项至少14次,但最多16次。
Grouping with parentheses 圆括号
在logback中,模式字符串中的括号被视为分组标记。因此,可以对子模式进行分组,并在该子模式上应用格式化指令。
For example, the pattern
%-30(%d{HH:mm:ss.SSS} [%thread]) %-5level %logger{32} - %msg%n
will group the output generated by the sub-pattern “%d{HH:mm:ss.SSS} [%thread]” so that it is right-padded if less than 30 characters.
假设没有分组输出是
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Classload hashcode is 1399523413:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Initializing for ServletContext13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server13:09:30 [pool-1-thread-1] INFO ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 013:09:38 [btpool0-7] INFO c.q.l.demo.lottery.LotteryAction - Number: 50 was tried.13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Beginning to factor.13:09:40 [btpool0-7] DEBUG c.q.l.d.prime.NumberCruncherImpl - Trying 2 as a factor.13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Found factor 2
with the “%-30()” grouping it would be
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Classload hashcode is 1399523413:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Initializing for ServletContext13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server13:09:30 [pool-1-thread-1] INFO ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 013:09:38 [btpool0-7] INFO c.q.l.demo.lottery.LotteryAction - Number: 50 was tried.13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Beginning to factor.13:09:40 [btpool0-7] DEBUG c.q.l.d.prime.NumberCruncherImpl - Trying 2 as a factor.13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Found factor 2
后一种形式读起来更舒服。
如果需要将括号字符视为文字,则需要在每个括号前面加上反斜杠进行转义。As in, (%d{HH:mm:ss.SSS} [%thread]).
Coloring
如上所述,用括号分组允许对子模式进行着色。 As of version 1.0.5, PatternLayout recognizes “%black”, “%red”, “%green”,”%yellow”,”%blue”, “%magenta”,”%cyan”, “%white”, “%gray”, “%boldRed”,”%boldGreen”, “%boldYellow”, “%boldBlue”, “%boldMagenta””%boldCyan”, “%boldWhite” and “%highlight” as conversion words. 这些转换词包含一个子模式。任何由着色词包围的子模式都将以指定的颜色输出。
下面是一个说明着色的配置文件。请注意包含“%logger{15}”的%cyan转换说明符。这将输出缩写为15个青色字符的记录器名称。%高亮显示转换说明符以粗体红色打印ERROR级别的事件的子模式,红色打印WARN级别的事件的子模式,蓝色打印INFO级别的事件的子模式,其他级别的事件的子模式以默认颜色打印。
<configuration debug="true"><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><withJansi>true</withJansi><encoder><pattern>[%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n</pattern></encoder></appender><root level="DEBUG"><appender-ref ref="STDOUT" /></root></configuration>
将Jansi设置为true使Jansi库能够解释ANSI颜色代码,如果底层终端不兼容,则会透明地过滤掉ANSI转义序列。对于跨平台部署来说,这是最安全的选择,但在类路径上需要org.fusesource.jansi:jansi:1.17或更高。请注意,基于unix的操作系统(如Linux和Mac OS X)本地支持ANSI颜色代码,通常不需要启用Jansi库,但这样做是无害的。然而,在Windows上,启用Jansi可以从DOS命令提示符上的颜色代码解释中获益,否则可能会发送无法解释的ANSI转义序列。
Here is the corresponding output:
[main] WARN c.l.TrivialMain - a warning message 0[main] DEBUG c.l.TrivialMain - hello world number1[main] DEBUG c.l.TrivialMain - hello world number2[main] INFO c.l.TrivialMain - hello world number3[main] DEBUG c.l.TrivialMain - hello world number4[main] WARN c.l.TrivialMain - a warning message 5[main] ERROR c.l.TrivialMain - Finish off with fireworks
只需很少的几行代码就可以创建一个着色转换字。标题为创建自定义转换说明符的部分讨论了在配置文件中注册转换字所需的步骤。
Evaluators
如上所述,当转换说明符需要基于一个或多个eventtevaluator对象动态行为时,选项列表会派上用场。eventtevaluator对象负责确定给定的日志事件是否符合评估器的标准。
让我们回顾一个涉及到eventtevaluator的例子。下一个配置文件将日志事件输出到控制台,显示日期、线程、级别、消息和调用者数据。由于提取日志事件的调用者数据代价很高,所以我们只在日志请求来自特定的日志记录器,且消息包含特定字符串时才这样做。因此,我们确保只生成和显示特定的日志请求的调用者信息。在其他情况下,调用者数据是多余的,我们不会降低应用程序的性能。
求值器,特别是求值表达式,在过滤器一章的专门章节中有介绍,如果你想以任何有意义的方式使用求值器,你必须阅读这个章节。还要注意,下面的例子是隐式地基于JaninoEventEvaluator的,它需要Janino库。请参阅安装文件的相应部分。
Example: Sample usage of EventEvaluators
<configuration><evaluator name="DISP_CALLER_EVAL"><expression>logger.contains("chapters.layouts") && \message.contains("who calls thee")</expression></evaluator><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%-4relative [%thread] %-5level - %msg%n%caller{2, DISP_CALLER_EVAL}</pattern></encoder></appender><root level="DEBUG"><appender-ref ref="STDOUT" /></root></configuration>
上面的求值表达式匹配来自名称包含字符串“章节”的日志记录器的事件。信息包含字符串“谁呼叫你”。由于XML编码规则,&字符不能按原样写,需要转义为&。
下面的类使用了上面配置文件中提到的一些特征。
Example: Sample usage of EventEvaluators
package chapters.layouts;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import ch.qos.logback.classic.LoggerContext;import ch.qos.logback.classic.joran.JoranConfigurator;import ch.qos.logback.core.joran.spi.JoranException;import ch.qos.logback.core.util.StatusPrinter;public class CallerEvaluatorExample {public static void main(String[] args) {Logger logger = LoggerFactory.getLogger(CallerEvaluatorExample.class);LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();try {JoranConfigurator configurator = new JoranConfigurator();configurator.setContext(lc);configurator.doConfigure(args[0]);} catch (JoranException je) {// StatusPrinter will handle this}StatusPrinter.printInCaseOfErrorsOrWarnings(lc);for (int i = 0; i < 5; i++) {if (i == 3) {logger.debug("who calls thee?");} else {logger.debug("I know me " + i);}}}}
上面的应用程序没有做什么特别奇特的事情。发出了5个日志记录请求,第三个请求发出消息“谁呼叫你?”
The command
java chapters.layouts.CallerEvaluatorExample src/main/java/chapters/layouts/callerEvaluatorConfig.xml
will yield
0 [main] DEBUG - I know me 00 [main] DEBUG - I know me 10 [main] DEBUG - I know me 20 [main] DEBUG - who calls thee?Caller+0 at chapters.layouts.CallerEvaluatorExample.main(CallerEvaluatorExample.java:28)0 [main] DEBUG - I know me 4
当发出日志请求时,将评估相应的日志事件。只有第三个日志记录事件符合评估标准,导致显示其调用者数据。对于其他日志记录事件,评估标准不匹配,也不会打印调用者数据。
可以更改表达式以对应真实场景。例如,可以组合日志记录器名称和请求级别。因此,WARN及以上级别的日志请求(来自应用程序的敏感部分,例如金融事务模块)将显示调用者的数据。
重要: 使用调用方转换字,当表达式计算为true时输出调用方数据。
让我们从不同的情况考虑。当日志请求中包含异常时,也会输出它们的堆栈跟踪。但是,对于某些特定的异常,可能希望禁止堆栈跟踪。
下面所示的Java代码创建了三个日志请求,每个请求都有一个例外。第二个异常与其他异常不同:它包含字符串“do not display this”,类型为chaps .layouts. testexception。作为它的消息命令,现在让我们阻止第二个异常被打印。
Example: Sample usage of EventEvaluators
package chapters.layouts;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import ch.qos.logback.classic.LoggerContext;import ch.qos.logback.classic.joran.JoranConfigurator;import ch.qos.logback.core.joran.spi.JoranException;import ch.qos.logback.core.util.StatusPrinter;public class ExceptionEvaluatorExample {public static void main(String[] args) {Logger logger = LoggerFactory.getLogger(ExceptionEvaluatorExample.class);LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();try {JoranConfigurator configurator = new JoranConfigurator();configurator.setContext(lc);lc.reset();configurator.doConfigure(args[0]);} catch (JoranException je) {// StatusPrinter will handle this}StatusPrinter.printInCaseOfErrorsOrWarnings(lc);for (int i = 0; i < 3; i++) {if (i == 1) {logger.debug("logging statement " + i, new TestException("do not display this"));} else {logger.debug("logging statement " + i, new Exception("display"));}}}}
In the next configuration file, the evaluation expression matches events containing a throwable of type chapters.layouts.TextException, precisely the type of exceptions we wish to suppress.
Example: Sample usage of EventEvaluators
<configuration><evaluator name="DISPLAY_EX_EVAL"><expression>throwable != null && throwable instanceof \chapters.layouts.TestException</expression></evaluator><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%msg%n%ex{full, DISPLAY_EX_EVAL}</pattern></encoder></appender><root level="debug"><appender-ref ref="STDOUT" /></root></configuration>
有了这个配置,每次在日志请求中包含一个chapter.layouts. testexception实例时,堆栈跟踪将被抑制。
Launching the command
java chapters.layouts.ExceptionEvaluatorExample src/main/java/chapters/layouts/exceptionEvaluatorConfig.xml
will yield
logging statement 0java.lang.Exception: displayat chapters.layouts.ExceptionEvaluatorExample.main(ExceptionEvaluatorExample.java:43) [logback-examples-0.9.19.jar:na]logging statement 1logging statement 2java.lang.Exception: displayat chapters.layouts.ExceptionEvaluatorExample.main(ExceptionEvaluatorExample.java:43) [logback-examples-0.9.19.jar:na]
注意第二个日志语句是如何没有堆栈跟踪的。我们有效地抑制了TextException的堆栈跟踪。每个堆栈跟踪行末尾方括号之间的文本打包了前面讨论的信息。
使用%ex转换说明符,当表达式计算为false时显示堆栈跟踪。
Creating a custom conversion specifier
到目前为止,我们已经介绍了PatternLayout中的内置转换词。但也可以添加你自己创造的转换词。
Step 1
首先,您必须扩展ClassicConverter类。ClassicConverter对象负责从ILoggingEvent实例中提取信息并生成一个String。例如,LoggerConverter是%logger转换字的基础转换器,它从ILoggingEvent中提取记录器的名称,并将其作为字符串返回。它可能在过程中缩写记录器名称。
下面是一个客户转换器,它以纳秒为单位返回自创建以来经过的时间:
Example: Sample Converter Example
public class MySampleConverter extends ClassicConverter {long start = System.nanoTime();@Overridepublic String convert(ILoggingEvent event) {long nowInNanos = System.nanoTime();return Long.toString(nowInNanos-start);}}
这个实现非常简单。MySampleConverter类扩展了ClassicConverter,并实现了convert方法,该方法返回自创建以来经过的纳秒数。
Step 2
在第二步中,我们必须让logback知道新的Converter。为此,我们需要在配置文件中声明新的转换字,如下图所示:
Example: Sample Converter Example
<configuration><conversionRule conversionWord="nanos"converterClass="chapters.layouts.MySampleConverter" /><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%-6nanos [%thread] - %msg%n</pattern></encoder></appender><root level="DEBUG"><appender-ref ref="STDOUT" /></root></configuration>
一旦在配置文件中声明了新的转换字,我们就可以在PatternLayout模式中引用它,就像使用其他任何转换字一样。
The command:
java chapters.layouts.SampleLogging src/main/java/chapters/layouts/mySampleConverterConfig.xml
should yield output akin to:
4868695 [main] DEBUG - Everything's going well5758748 [main] ERROR - maybe not quite...
读者可能想看一看其他转换器实现,如MDCConverter,以了解更复杂的行为,如选项处理。要创建自己的着色方案,请查看HighlightingCompositeConverter。
HTMLLayout
HTMLLayout(包括在logback-classic中)生成HTML格式的日志。HTMLLayout在HTML表格中输出日志事件,表格的每一行都对应一个日志事件。
下面是HTMLLayout使用默认CSS样式表生成的输出示例:
在转换模式的帮助下指定表列的内容。有关转换模式的文档,请参阅PatternLayout。因此,您可以完全控制表格的内容和格式。您可以选择和显示PatternLayout知道的任何转换器组合。
在HTMLLayout中使用PatternLayout有一个值得注意的例外,那就是转换说明符不应该由空格字符或更一般地由字面文本分隔。在模式中找到的每个说明符将导致一个单独的列。同样,将为模式中找到的每个文本块生成单独的列,可能会在屏幕上浪费宝贵的空间。
下面是一个简单但实用的配置文件,演示了HTMLLayout的使用。
Example: HTMLLayout Example
<configuration debug="true"><appender name="FILE" class="ch.qos.logback.core.FileAppender"><encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"><layout class="ch.qos.logback.classic.html.HTMLLayout"><pattern>%relative%thread%mdc%level%logger%msg</pattern></layout></encoder><file>test.html</file></appender><root level="DEBUG"><appender-ref ref="FILE" /></root></configuration>
TrivialMain应用程序记录了一些以异常结束的消息。命令:
java chapters.layouts.TrivialMain src/main/java/chapters/layouts/htmlLayoutConfig1.xml
将在当前文件夹中创建文件test.html。test.html的内容应该类似于:
Stack traces
如果使用%em转换字来显示堆栈跟踪,则将创建一个表列来显示堆栈跟踪。在大多数情况下,列将为空,浪费屏幕空间。此外,在单独的列上打印堆栈跟踪不会产生非常可读的结果。幸运的是,%ex转换字并不是显示堆栈跟踪的唯一方法。
通过实现IThrowableRenderer接口可以获得更好的解决方案。这种实现可以分配给HTMLLayout来管理与异常相关的显示数据。默认情况下,DefaultThrowableRenderer被分配给每个HTMLLayout实例。它以一种容易读懂的方式将异常及其堆栈跟踪写入新表行,如上图所示。
如果出于某种原因,您仍然希望使用%ex模式,那么您可以在配置文件中指定NOPThrowableRenderer,以禁用为堆栈跟踪显示单独的行。我们完全不知道你为什么要这么做,但如果你愿意,你可以。
CSS
HTMLLayout创建的HTML的表示是通过层叠样式表(CSS)控制的。在没有具体说明的情况下,HTMLLayout将默认为它的内部CSS。但是,您可以指示HTMLLayout使用外部CSS文件。为此目的,cssBuilder元素可以嵌套在
<layout class="ch.qos.logback.classic.html.HTMLLayout"><pattern>%relative...%msg</pattern><cssBuilder class="ch.qos.logback.classic.html.UrlCssBuilder"><!-- url where the css file is located --><url>http://...</url></cssBuilder></layout>
HTMLLayout通常与SMTPAppender一起使用,这样发送的电子邮件就会以HTML格式愉快地格式化。
Log4j XMLLayout
XMLLayout(经典logback的一部分)在log4j中生成输出。dtd兼容的格式,可以与Chainsaw和Vigilog等工具进行互操作,这些工具能够处理由log4j的XMLLayout生成的文件。
作为log4j版本1.2.15中的原始XMLLayout, logback-classic中的XMLLayout有两个布尔属性,locationInfo和properties。将locationInfo设置为true启用在每个事件中包含位置信息(调用者数据)。将属性设置为true允许包含MDC信息。默认情况下,这两个选项都设置为false。
下面是一个示例配置
Example: Log4jXMLLayout Example
<configuration><appender name="FILE" class="ch.qos.logback.core.FileAppender"><file>test.xml</file><encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"><layout class="ch.qos.logback.classic.log4j.XMLLayout"><locationInfo>true</locationInfo></layout></encoder></appender><root level="DEBUG"><appender-ref ref="FILE" /></root></configuration>
Logback access
大多数logback-access布局仅仅是logback-classic布局的改编。Logback-classic和logback-access模块解决了不同的需求,但通常提供类似的功能。
Writing your own Layout
为logback访问编写自定义布局几乎与logback-classic中的同级布局完全相同。
PatternLayout
logback-access中的PatternLayout可以像它的经典版本一样进行配置。但是,它提供了额外的转换说明符,适合记录只在HTTP servlet请求和HTTP servlet响应中可用的特定信息位。
下面是logback-access中PatternLayout的转换说明符列表。
