logback中的Layouts
# 简介
Layout就是负责将事件对象转换为String对象的,而且支持自定义,你可以把日志转换为其他格式html,json等等。
public interface Layout<E> extends ContextAware, LifeCycle {
String doLayout(E event);
String getFileHeader();
String getPresentationHeader();
String getFileFooter();
String getPresentationFooter();
String getContentType();
}
2
3
4
5
6
7
8
9
# 自定义Layout
public class MyLayout extends LayoutBase<ILoggingEvent> {
@Override
public String doLayout(ILoggingEvent event) {
return "自定义布局器:"+ (event.getTimeStamp() - event.getLoggerContextVO().getBirthTime())
+ " "
+ event.getLevel()
+ " ["
+ event.getThreadName()
+ "] "
+ event.getLoggerName()
+ " - "
+ event.getFormattedMessage()
+ CoreConstants.LINE_SEPARATOR;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<configuration debug="false" scan="false" scanPeriod="1 second" packagingData="false">
<property name="pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger) - %cyan(%msg%n)"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="com.unclezs.samples.log.slf4j.logback.layouts.MyLayout"/>
<!--立即刷新到流-->
<immediateFlush>true</immediateFlush>
</encoder>
</appender>
<logger name="com.unclezs.samples.log.slf4j.logback" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
<root level="off">
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 本文代码
log-slf4j-logback (opens new window)
# PatternLayout
logback 配备了一个更加灵活的 layout 叫做 PatternLayout。跟所有的 layout 一样,PatternLayout 接收一个日志事件并返回一个字符串。但是,可以通过调整 PatternLayout 的转换模式来进行定制。
PatternLayout 中的转换模式与 C 语言中 printf() 方法中的转换模式密切相关。转换模式由字面量与格式控制表达式也叫转换说明符组成。你可以在转换模式中自由的插入字面量。每一个转换说明符由一个百分号开始 '%',后面跟随可选的格式修改器,以及用综括号括起来的转换字符与可选的参数。转换字符需要转换的字段。如:logger 的名字,日志级别,日期以及线程名。格式修改器控制字段的宽度,间距以及左右对齐。
正如我们已经在其它地方提到过的,FileAppender 及其子类需要一个 encoder。因为,当将 FileAppender 及其子类与 PatternLayout 结合使用时,PatternLayout 必须用 encoder 包裹起来。鉴于 FileAppender/PatternLayout 结合使用很常见,因此 logback 单独设计了一个名叫 PatternLayoutEncoder 的 encoder,包裹了一个 PatternLayout,因此它可以被当作一个 encoder。下面是通过代码配置 ConsoleAppender 与 PatternLayoutEncoder 使用的例子:
public class PatternSample {
static public void main(String[] args) throws Exception {
Logger rootLogger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
LoggerContext loggerContext = rootLogger.getLoggerContext();
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");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
转化后输出:
DEBUG [main]: Message 1
WARN [main]: Message 2
2
# 格式化日志的写法
格式化字符 | 含义 |
---|---|
%logger{length} | logger的名字,长度是最大长度,如果全限定类名超过长度了就进行简写,但是类名不会被简写 eg: %logger{3}: com.unclezs.Test=>c.u.Test |
%class{length} | 调用者的全限定类名,通过计算的,比价慢,一般不用 |
%contextName | 输出日志事件附加到的 logger 上下文的名字 |
%date{pattern,[timezone]} | 时区可选,显示当前时间 |
caller{depth}、caller{depthStart..depthEnd}、caller{depth, evaluator-1, ... evaluator-n}、caller{depthStart..depthEnd, evaluator-1, ... evaluator-n} | %caller{2} 显示2层调用栈 |
%line | 行号,效率低,不建议使用 |
%n | 换行 |
%level | 日志级别 |
%relative | 打日志消耗时间毫秒数 |
%thread | 输出生成日志事件的线程名。 |
m / msg / message | 日志信息 |
method | 调用打印日志的所在方法名字,比较慢,不建议用 |
X/mdc | 如果 MDC 转换字符后面跟着用花括号括起来的 ,例 %mdc{userid},那么 'userid' 所对应 MDC 的值将会输出。如果该值为 null,那么通过 :- 指定的默认值 将会输出。如果没有指定默认值,那么将会输出空字符串。 如果没有指定的 key,那么 MDC 的整个内容将会以 "key1=val1, key2=val2" 的格式输出。 |
exception{depth}/throwable{depth} | depth: short第一行、full全部、数字指定几行。 |
%xException /xE/xThrowbale | 和exception一样,不过会在每行后面显示jar包名字和版本号 |
%rootException | 和xeception一样,反向输出 |
marker | 输出相关的标签 |
property{key} | 输出属性值 |
replace(p){r, t} | 在子模式 'p' 产生的字符中,将所有出现正则表达式 'r' 的地方替换为 't'。例如,"%replace(%msg){'\s', ''}" 将会移除事件消息中所有空格。 |
# 对齐方式
默认情况下,相关信息按照原样输出。但是,在格式修改器的帮助下,可以对每个数据字段进行对齐,以及更改最大最小宽度。
可选的格式修改器放在百分号跟转换字符之间。
第一个可选的格式修改器是左对齐标志,也就是减号 (-) 字符。接下来的是最小字段宽度修改器,它是一个十进制常量,表示输出至少多少个字符。如果字段包含很少的数据,它会选择填充左边或者右边,直到满足最小宽度。默认是填充左边 (右对齐),但是你可以通过左对齐标志来对右边进行填充。填充字符为空格。如果字段的数据大于最小字段的宽度,会自动扩容去容纳所有的数据。字段的数据永远不会被截断。
这个行为可以通过使用最大字段宽度修改器来改变,它通过一个点后面跟着一个十进制常量来指定。如果字段的数据长度大于最大字段的宽度,那么会从数据字段的开头移除多余的字符。举个🌰,如果最大字段的宽度是 8,数据长度是十个字符的长度,那么开头的两个字符将会被丢弃。这个行为跟 C 语言中 printf 函数从后面开始截断的行为相违背。
如果想从后面开始截断,可以在点后面增加一个减号。如果是这样的话,最大字段宽度是 8,数据长度是十个字符的长度,那么最后两个字符将会被丢弃。
下面是各种格式修改器的例子:
格式修改器 | 左对齐 | 最小宽度 | 最大宽度 | 备注 |
---|---|---|---|---|
%20logger | false | 20 | none | 如果 logger 的名字小于 20 个字符的长度,那么会在左边填充空格 |
%-20logger | true | 20 | none | 如果 logger 的名字小于 20 个字符的长度,那么会在右边填充空格 |
%.30logger | NA | none | 30 | 如果 logger 的名字大于 30 个字符的长度,那么从前面开始截断 |
%20.30logger | false | 20 | 30 | 如果 logger 的名字大于 20 个字符的长度,那么会从左边填充空格。但是如果 logger 的名字大于 30 字符,将会从前面开始截断 |
%-20.30logger | true | 20 | 30 | 如果 logger 的名字小于 20 个字符的长度,那么从右边开始填充空格。但是如果 logger 的名字大于 30 个字符,将会从前面开始截断 |
%.-30logger | NA | none | 30 | 如果 logger 的名字大于 30 个字符的长度,那么从后面开始截断 |
下面的表格列出了格式修改器截断的例子。但是请注意综括号 "[]" 不是输出结果的一部分,它只是用来区分输出的长度:
格式修改器 | logger 的名字 | 结果 |
---|---|---|
[%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] |
# 转义字符的选项
<pattern>%-5level - %replace(%msg){'\d{14,16}', 'XXXX'}%n</pattern>
我们传递 \d{16} 与 XXXX 给 replace 转换字符。它将消息中 14,15 或者 16 位的数字替换为 XXXX,用来混淆信用卡号码。在正则表达式中,"\d" 表示一个数字的简写。"{14,16}" 会被解析成 "{14,16}",也就是说前一个项将会被重复至少 14 次,至多 16 次。
# 特殊的圆括号
在 logback 里,模式字符串中的圆括号被看作为分组标记。因此,它能够对子模式进行分组,并且直接对子模式进行格式化。在 0.9.27 版本,logback 开始支持综合转换字符,例如 %replace 可以对子模式进行转换。
例如一下模式:
%-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
13: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
2
3
4
5
6
7
8
# 高亮彩色日志
如上所述的圆括号分组,允许对子模式进行着色。在 1.0.5 版本,PatternLayout 可以识别 "%black","%red","%green","%yellow","%blue","%magenta","%cyan", "%white", "%gray", "%boldRed","%boldGreen", "%boldYellow", "%boldBlue", "%boldMagenta""%boldCyan", "%boldWhite" 以及 "%highlight" 作为转换字符。这些转换字符都还可以包含一个子模式。任何被颜色转换字符包裹的子模式都会通过指定的颜色输出。
然后在pattern中使用即可。
# Evaluators
可以用来动态判断是否需要显示某些信息,比如异常信息,调用信息。
由于 XML 的编码规则,& 符号需要被转义为 &
判断打印的消息中是否包含different字符串:
<configuration debug="false" scan="false" scanPeriod="1 second" packagingData="false">
<evaluator name="isError">
<expression>message.contains("different")</expression>
</evaluator>
<property name="pattern"
value=" %-4relative [%thread] %-5level - %msg%n%caller{2, isError}"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
<!--在头部打印出pattern-->
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
<!--立即刷新到流-->
<immediateFlush>true</immediateFlush>
</appender>
<logger name="com.unclezs.samples.log.slf4j.logback" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
<root level="off">
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
741 [main] INFO - test message by different level
Caller+0 at com.unclezs.samples.log.slf4j.logback.utils.LoggerHelper.logMsg(LoggerHelper.java:46)
Caller+1 at com.unclezs.samples.log.slf4j.logback.utils.LoggerHelper.logMsg(LoggerHelper.java:57)
2
3
# 自定义转换说明符
我们可以在 PatternLayout 中使用内置的转换字符。我们也可以使用自己新建的转换字符。
新建一个自定义的转换字符需要两步。
1. 自定义
首先,你必须继承 ClassicConverter 类。ClassicConverter 对象负责从 ILoggingEvent 实例中抽取信息并输出字符串。例如,%logger 对应的转换器 LoggerConverter,可以从 ILoggingEvent 从抽取 logger 的名字,返回一个字符串。它可以缩写 logger 的名字。
下面是一个自定义的转换器,获取当前时间戳。
/**
* @author blog.unclezs.com
* @since 2020/12/04 17:03
*/
public class CustomNowTimeConverter extends ClassicConverter {
@Override
public String convert(ILoggingEvent event) {
return String.valueOf(System.currentTimeMillis());
}
}
2
3
4
5
6
7
8
9
10
11
2. 配置
<configuration debug="false" scan="false" scanPeriod="1 second" packagingData="false">
<conversionRule conversionWord="nowTime"
converterClass="com.unclezs.samples.log.slf4j.logback.converter.CustomNowTimeConverter" />
<property name="pattern"
value="%nowTime - %cyan(%msg%n)"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
<!--在头部打印出pattern-->
<outputPatternAsHeader>false</outputPatternAsHeader>
</encoder>
<!--立即刷新到流-->
<immediateFlush>true</immediateFlush>
</appender>
<logger name="com.unclezs.samples.log.slf4j.logback" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
<root level="off">
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3. 输出
1607072991810 - test message by different level
1607072991810 - test message by different level
1607072991810 - test message by different level
2
3
# 其他
- HTMLLayout
- XMLLayout
- 还有Logback Access包中也有提供
# 了解更多
Chapter 6: Layouts (opens new window) 示例代码 (opens new window)