简介

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();
}

自定义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;
  }
}
<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>

本文代码

log-slf4j-logback

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");
  } 
}

转化后输出:

DEBUG [main]: Message 1 
WARN  [main]: Message 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

高亮彩色日志

如上所述的圆括号分组,允许对子模式进行着色。在 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>
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)

自定义转换说明符

我们可以在 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. 配置

<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>

3. 输出

1607072991810 - test message by different level
1607072991810 - test message by different level
1607072991810 - test message by different level

其他

  • HTMLLayout
  • XMLLayout
  • 还有Logback Access包中也有提供

了解更多

Chapter 6: Layouts
示例代码

评论

博客
分类
标签
归档
关于