跳至主要內容

Layouts

Jin大约 9 分钟

Layouts

1、什么是 layout?

layout 是 logback 的组件,负责将日志事件转换为字符串。Layoutopen in new window 接口中的 format() 方法接受一个表示日志事件的对象 (任何类型) 并返回一个字符串。Layout 接口的概要如下:

package org.apache.log4j;

import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.OptionHandler;

public abstract class Layout implements OptionHandler {
    public static final String LINE_SEP = System.getProperty("line.separator");
    public static final int LINE_SEP_LEN;

    public Layout() {
    }

    public abstract String format(LoggingEvent var1);

    public String getContentType() {
        return "text/plain";
    }

    public String getHeader() {
        return null;
    }

    public String getFooter() {
        return null;
    }

    public abstract boolean ignoresThrowable();

    static {
        LINE_SEP_LEN = LINE_SEP.length();
    }
}

2、格式修改器

默认情况下,相关信息按照原样输出。但是,在格式修改器的帮助下,可以对每个数据字段进行对齐,以及更改最大最小宽度。

可选的格式修改器放在百分号跟转换字符之间。

第一个可选的格式修改器是左对齐标志,也就是减号 (-) 字符。接下来的是最小字段宽度修改器,它是一个十进制常量,表示输出至少多少个字符。如果字段包含很少的数据,它会选择填充左边或者右边,直到满足最小宽度。默认是填充左边 (右对齐),但是你可以通过左对齐标志来对右边进行填充。填充字符为空格。如果字段的数据大于最小字段的宽度,会自动扩容去容纳所有的数据。字段的数据永远不会被截断。

这个行为可以通过使用最大字段宽度修改器来改变,它通过一个点后面跟着一个十进制常量来指定。如果字段的数据长度大于最大字段的宽度,那么会从数据字段的开头移除多余的字符。举个🌰,如果最大字段的宽度是 8,数据长度是十个字符的长度,那么开头的两个字符将会被丢弃。这个行为跟 C 语言中 printf 函数从后面开始截断的行为相违背。

如果想从后面开始截断,可以在点后面增加一个减号。如果是这样的话,最大字段宽度是 8,数据长度是十个字符的长度,那么最后两个字符将会被丢弃。

下面是各种格式修改器的例子:

格式修改器左对齐最小宽度最大宽度备注
%20loggerfalse20none如果 logger 的名字小于 20 个字符的长度,那么会在左边填充空格
%-20loggertrue20none如果 logger 的名字小于 20 个字符的长度,那么会在右边填充空格
%.30loggerNAnone30如果 logger 的名字大于 30 个字符的长度,那么从前面开始截断
%20.30loggerfalse2030如果 logger 的名字大于 20 个字符的长度,那么会从左边填充空格。但是如果 logger 的名字大于 30 字符,将会从前面开始截断
%-20.30loggertrue2030如果 logger 的名字小于 20 个字符的长度,那么从右边开始填充空格。但是如果 logger 的名字大于 30 个字符,将会从前面开始截断
%.-30loggerNAnone30如果 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]

只输出日志等级的一个字符

除了可以输出 TRACE, DEBUG, WARN, INFO 或者 ERROR 来表示日志等级之外,还是输出T, D, W, I 与 E 来进行表示。你可以自定义转换器open in new window 或者利用刚才讨论的格式修改器来缩短日志级别为一个字符。这个转换说明符可能为 "%.-1level"。

3、转换字符的选项

一个转换字符后面可以跟一个选项。它们通过综括号来声明。我们之前已经看到了一些可能的选项。例如之前的 MDC 转换说明符 %mdc{someKey}

一个转换说明符可能有多个可选项。一个转换说明符可以充分利用我们即将介绍到的 evaluator,可以添加多个 evaluator 的名字到可选列表。如下:

<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}XXXXreplace 转换字符。它将消息中 14,15 或者 16 位的数字替换为 XXXX,用来混淆信用卡号码。在正则表达式中,"\d" 表示一个数字的简写。"{14,16}" 会被解析成 "{14,16}",也就是说前一个项将会被重复至少 14 次,至多 16 次。

4、特殊的圆括号

在 logback 里,模式字符串中的圆括号被看作为分组标记。因此,它能够对子模式进行分组,并且直接对子模式进行格式化。在 0.9.27 版本,logback 开始支持综合转换字符,例如 %replaceopen in new window 可以对子模式进行转换。

例如一下模式:

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

如果对 "%-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

后者的格式更加容易阅读。

如果你想将圆括号当作字面量输出,那么你需要对每个圆括号用反斜杠进行转义。就像 (%d{HH:mm:ss.SSS} [%thread]) 一样。

5、着色

如上所述的圆括号open in new window分组,允许对子模式进行着色。在 1.0.5 版本,PatternLayout 可以识别 "%black","%red","%green","%yellow","%blue","%magenta","%cyan", "%white", "%gray", "%boldRed","%boldGreen", "%boldYellow", "%boldBlue", "%boldMagenta""%boldCyan", "%boldWhite" 以及 "%highlight" 作为转换字符。这些转换字符都还可以包含一个子模式。任何被颜色转换字符包裹的子模式都会通过指定的颜色输出。

PatternLayout描述
black黑色
red红色
green绿色
yellow黄色
blue蓝色
magenta品红
cyan青色
white白色
gray灰色
boldRed
boldGreen
boldYellow
boldBlue
boldMagenta
boldCyan
boldWhite
highlight

面是关于着色的配置文件。

<configuration debug="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--               在 Windows 平台下,设置 withJansi = true 来开启 ANSI 颜色代码需要 Jansi 类库 -->
<!--               需要在 classpath 引入 org.fusesource.jansi:jansi:1.8 包 -->
<!--               在基于 Unix 操作系统,像 Linux 以及 Mac OS X 系统默认支持 ANSI 颜色代码 -->
        <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>

5.1、Spring 提供的转换器

ColorConverter 源码

public class ColorConverter extends CompositeConverter<ILoggingEvent> {

	private static final Map<String, AnsiElement> ELEMENTS;

	static {
		Map<String, AnsiElement> ansiElements = new HashMap<>();
		ansiElements.put("black", AnsiColor.BLACK);
		ansiElements.put("white", AnsiColor.WHITE);
		ansiElements.put("faint", AnsiStyle.FAINT);
		ansiElements.put("red", AnsiColor.RED);
		ansiElements.put("green", AnsiColor.GREEN);
		ansiElements.put("yellow", AnsiColor.YELLOW);
		ansiElements.put("blue", AnsiColor.BLUE);
		ansiElements.put("magenta", AnsiColor.MAGENTA);
		ansiElements.put("cyan", AnsiColor.CYAN);
		ansiElements.put("bright_black", AnsiColor.BRIGHT_BLACK);
		ansiElements.put("bright_white", AnsiColor.BRIGHT_WHITE);
		ansiElements.put("bright_red", AnsiColor.BRIGHT_RED);
		ansiElements.put("bright_green", AnsiColor.BRIGHT_GREEN);
		ansiElements.put("bright_yellow", AnsiColor.BRIGHT_YELLOW);
		ansiElements.put("bright_blue", AnsiColor.BRIGHT_BLUE);
		ansiElements.put("bright_magenta", AnsiColor.BRIGHT_MAGENTA);
		ansiElements.put("bright_cyan", AnsiColor.BRIGHT_CYAN);
		ELEMENTS = Collections.unmodifiableMap(ansiElements);
	}

开启 clr 转换规则

<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>

配置

<pattern>%-250(${CONTEXT_NAME} ${ip} %clr(%d{HH:mm:ss.SSS}){red} %clr(${LOG_LEVEL_PATTERN:-%-5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr(%applicationName[%-15.15t]){faint} %clr(%-40.40logger{39}){cyan} %boldYellow(%M) %clr(:){bright_yellow} ) %m%n%wEx</pattern>

6、自定义转换说明符

定义的转换字符需要两步

第一步

首先,你必须继承 ClassicConverter 类。ClassicConverteropen in new window 对象负责从 ILoggingEvent 实例中抽取信息并输出字符串。例如,%logger 对应的转换器 LoggerConverteropen in new window,可以从 ILoggingEvent 从抽取 logger 的名字,返回一个字符串。它可以缩写 logger 的名字。

下面是一个自定义的转换器,返回从创建开始经过的时间,单位为纳秒。

Example: NanoConverter

import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;

public class NanoConverter extends ClassicConverter {

    long start = System.nanoTime();

    @Override
    public String convert(ILoggingEvent event) {
        long nowInNanos = System.nanoTime();
        return Long.toString(nowInNanos - start);
    }
}

这个实现非常简单。NanoConverter 继承了 ClassicConverter 并实现了 convert 方法,返回从创建开始经过多少纳秒。

第二步

我们必须让 logback 知道这个新建的 Converter。所以我们需要在配置文件中进行声明,如下:

<configuration>

    <!--    自定义转换说明符-->
    <conversionRule conversionWord="nanos"
                    converterClass="com.jin.logging.converter.NanoConverter"/>

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

输出效果

26113953 [main] - Everything's going well
26672034 [main] - maybe not quite...

7、HTMLLayout

HTMLLayoutopen in new window (包含在 logback-classic 中) 以 HTML 格式生成日志。HTMLLayout 通过 HTML 表格输出日志,每一行对应一条日志事件。

表格的列是通过转换模式指定的。关于转换模式的文档请查看 PatternLayoutopen in new window。所以,你可以完全控制表格的内容以及格式。你可以选择并且展示任何跟 PatternLayout 组合的转换器。

<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>%d{MM-dd HH:mm:ss.SSS}%thread%5level%logger{50}%msg%n</pattern>
      </layout>
    </encoder>
    <file>test.html</file>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

注意事项:不要使用空格或者其它的字面量来分隔转换说明符。转换模式中的每个说明符都会被当做一个单独的列。同样的转换模式中的每个文本块也会被当作一个单独的列,这会占用屏幕的空间。

空格效果对比

无空格 pattern 表达式

<pattern>%d{MM-dd HH:mm:ss.SSS}%thread%5level%logger{50}%msg%n</pattern>

效果如下:

image-20240531010736928
image-20240531010736928

有空格 pattern 表达式

<pattern>%d{MM-dd HH:mm:ss.SSS} %thread %5level %logger{50} %msg%n</pattern>

效果如下:

image-20240531010902143
image-20240531010902143

8、其他转换说明符

%clr 使用%clr转换字配置颜色编码  如:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}
%c 输出logger名称
%C 输出类名
%d{HH:mm:ss.SSS} 表示输出到毫秒的时间
%t 输出当前线程名称
%-5level 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
%logger 输出logger名称,因为Root Logger没有名称,所以没有输出
%msg 日志文本
%n 换行
其他常用的占位符有:
%F 输出所在的类文件名,如Log4j2Test.java
%L 输出行号
%M或%method 输出所在方法名
%l 输出完整的错误位置, 包括类名、方法名、文件名、行数
%p 该条日志的优先级
%replace{pattern}{regex}{substitution} 将pattern的输出结果pattern按照正则表达式regex替换成substitution
贡献者: Jin