Encoders
就是把 LoggingEvent
转化为字节数组并向 OutputStream
(输出流)写出的这么个东东.
Layouts
就是能够把 LoggingEvent
转换成字符串的东东.
相比较之下, Layouts
既不能控制 LoggingEvent
何时输出, 也不能把 LoggingEvent
聚合成批(batches). 但是 Encoder
既能控制 LoggingEvent
输出格式,也能控制 LoggingEvent
何时输出.
不过PatternLayoutEncoder
才是真正实践中使用的encoder
. 它是对PatternLayout
的包装.
咋说呢, encoder
的抽象有点欠缺,有啥不干脆把 layout
的职责也涵盖了算了?
Encoder
有2个作用:
LoggingEvent
为字节数组OutputStream
输出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}.
* 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;
}
LayoutWrappingEncoder
bridges(桥接) the gap between encoders and layouts. It implements the encoder interface and wraps a layout to which it delegates the work of transforming an event into string.
(注: LayoutWrappingEncoder
的出现本质上是历史设计缺陷, 其实 Layout
是没有必要单独拎出来的抽象, 而是完全可以作为 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);
}
}
}
PatternLayoutEncoder
是应用最为广泛 的layout
,也是对 LayoutWrappingEncoder
的扩展(扩展了PatternLayout
职责). 从 0.9.19 版本的logback 开始, FileAppender
及其子类只要配置PatternLayout
就一定会使用 PatternLayoutEncoder
.
Layouts
就是把 LoggingEvent
格式化成字符串的组件(component).
我们看看接口定义:
public interface Layout<E> extends ContextAware, LifeCycle {
String doLayout(E event);
String getFileHeader();
String getPresentationHeader();
String getFileFooter();
String getPresentationFooter();
String getContentType();
}
在logback-classic里实际只会处理 ILoggingEvent 类型的 Event,而不会处理 AccessEvent类型的Event
我们可以自己写一个 Layout
,但坦白说, 官方给出来的 Layout
已经能够满足日常需要了.往深了想, 打个日志而已, 把格式打出花来,那不也只是日志吗?
logback-classic 有一个很灵活的PatternLayout
,当然它的主要职责也是拿到一个logging event
,返回一个字符串.只不过这个字符串是被 PatternLayout
格式化的. 格式化就是所谓的conversion pattern
.
logback的 conversion pattern
和 C语言的printf 很类似.
前面已经提到过, 但 FileAppender
及其子类需要一个Encoder
.比如常用的ConsoleAppender
就使用了PatternLayoutEncoder
.
我们看个栗子:
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-configuration
loggerContext.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");
}
这里的conversion pattern
就是 %-5level [%thread]: %message%n
这一坨.
conversion specifier
格式形如 %mdc{someKey}
, 这个 specifier 能够获取 MDC
中的K-V.
conversion specifier
还可以有多种 选项(option
). 比如 conversion specifier
还能够利用 自定义evluators
定制 pattern layout (当前最新版本 1.3.X 已经支持了吗?)
<pattern>%-4relative [%thread] %-5level - %msg%n %caller{2, DISP_CALLER_EVAL, OTHER_EVAL_NAME, THIRD_EVAL_NAME}</pattern>
使用括号 ()
包起来的内容叫做"grouping tokens",比如:
%-30(%d{HH:mm:ss.SSS} [%thread]) %-5level %logger{32} - %msg%n
假如 "%d{HH:mm:ss.SSS} [%thread]"
少于 30 各方字符就会自动填充, 这样日志看起来清爽很多,比如:
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Classload hashcode is 13995234
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Initializing for ServletContext
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server
13:09:30 [pool-1-thread-1] INFO ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 0
EventEvaluator
EventEvaluator
是个 java interface
.它就是用来决定一个 LoggingEvent
是否满足EventEvaluator
的条件来决定是否输出这条日志的.
不过其实很少会这样用到. 假如我希望不输出这条日志,那么干啥调 logger.info
这类API呢?
Converter
是抽象类.
关于Converter
抽象类的文档中解释是:
A minimal converter which sets up the general interface for derived classes. It also implements the functionality to chain converters in a linked list
说了等于没说.
其实Coverter
的抽象很好理解,比如我希望在Pattern
中动态加入一些自定义的属性(变量). 看这个例子它能记录 Converter
创建的时间到记录日志时间差.
步骤一:
public class MySampleConverter extends ClassicConverter {
long start = System.nanoTime();
@Override
public String convert(ILoggingEvent event) {
long nowInNanos = System.nanoTime();
return Long.toString(nowInNanos-start);
}
}
步骤二:
<configuration>
<conversionRule conversionWord="nanos"
converterClass="chapters.layouts.MySampleConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 注意这里的 %6-nanos -->
b <pattern>%-6nanos [%thread] - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
然后输出日志格式,所有日志前都带了纳秒时间:
4868695 [main] DEBUG - Everything's going well
5758748 [main] ERROR - maybe not quite...
另外,有一个MDCConverter
是比较特殊的.
logback-access
的PatternLayout
和 logback-classic
十分类似; 但是 前者会有更多的 conversion specifiers
, 尤其是因为 HTTP的 request/response而定制的 conversion specifiers
.
比如:
Conversion Word | feature |
---|---|
a / remoteIP | Remote IP address. |
b / B / bytesSent | Response’s content length |
h / clientHost | Remote host. |
H / protocol | Request protocol. |
r / requestURL | URL requested. |
i{header} / header{header} | Request header. |
等等.
.