系列
实现自定义SpringBoot框架日志组件の一:日志系统
实现自定义SpringBoot框架日志组件の二:配置文件
实现自定义SpringBoot框架日志组件の三: 自定义pattern
实现自定义SpringBoot框架日志组件の四: 自适应
前言
前面我们自定义了输出格式,能够输出了,不过输出的还有一些瑕疵,这篇博客就来优化一下
目标:
- 输出带有方法名和行号
- 输出形式类似 logback的那种自动伸缩
pattern
上一篇博客,我直接贴出来了配置,但是没有详细讲解,因为这篇博客会去扩展pattern,所以这里简单介绍一下
%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%5p} [%clr{%23t}{magenta}] %highlight{%-40.40c{1.}} %clr{:}{faint} %m%n%ex
-
%d
输出时间,全称是%date
,后面接的参数是时间格式 -
%highlight{%5p}
%p
是日志级别,全称是%level
(不知道为什么没有对上),%5p
输出长度最小为5,长度不够左侧补空格,想要右侧补空格就是%5-p
,%highlight
颜色高亮,高亮配色是log4j2内置的,当然了你也可以自定义,如何配置请看官网Pattern Layout (网页记得往下拉,内容在中间) -
[%clr{%23t}{magenta}]
%t
输出线程name;%23t
输出23长度,不够左侧补空格;%clr{xxx}{magenta}
xxx输出固定的颜色magenta
,颜色name列表还是看上面的官网链接;[]
输出中括号 -
%highlight{%-40.40c{1.}}
%c
是包名+类名;-40
是最小长度40,不够右侧补空格(删除-
就是左侧补空格),超出了就超出了;.40
就是最大长度40,如果超出那就截掉左侧多余的;c{1.}
就是只完整输出类名,每个包名全部简略输出缩短为1,同理c{2.}
就是每个包名缩短为2;%highlight
颜色高亮 -
%ex
异常的输出格式;没有异常的时候,这个不生效,有异常的时候,异常按照这个格式输出;不配置的话,默认是%xEx
,此时会打印异常的扩展信息(JAR名称和版本),可能导致业务线程Block, 详见 美团技术
下图是pattern %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%5p} [%clr{%23t}{magenta}] %highlight{%-40c} | %highlight{%.40c} | %highlight{%-40.40c{1.}} %clr{:}{faint} %m%n%ex
的输出,大家感受一下
logback
我们运行一下 web-default
,这个工程什么都没有配置,所以日志组件是 logback
的,我们看一下效果
可以看出来类名的输出效果是:
- 对齐,满足最大长度的限制
- 满足1的前提下,从右往左尽量输出多的包名
而 log4j2 的输出行为,只实现了功能1,没有功能2,
方法和行号
%M
方法
%L
行号
这两个输出的时候,需要得到 location
信息,性能会降低一些
张哈希大佬给出了一个思路,大家可以参考 在被线上大量日志输出导致性能瓶颈毒打了很多次之后总结出的经验
我这里是不想那么麻烦,降低就降低吧,直接拿来用了
注意:log4j2 默认是不包含位置信息 location 的,也就是说
%M
%L
默认情况下不会输出,需要主动开启,如以下配置
自定义 pattern
前面说了这么多,是为了介绍背景知识,一切都是为了实现我们的目标,这里再描述一下目标
目标:
- 输出带有方法名和行号
- 输出形式类似 logback的那种自动伸缩
为了实现目标1,我们的 pattern 就是 [%c.%M():%L]
但是因为log4j2 不支持自动伸缩,那没办法,只好自行实现一个了 Conversion Pattern
了
我们现在定义一个表达式吧,就叫 prettyClass
, 意思是更优雅的类输出
再加一个长度限制,但是不想那么麻烦,就只支持纯数字的吧,写法是这样的 prettyClass
PrettyClassPatternConverter
那么该从哪里开始呢?
在张哈希的那篇博客里,我们看到他实现了一个自定义异常格式化插件
不难看出,log4j2 的异常默认插件就是 ThrowablePatternConverter
, 且能猜到这个类的兄弟类都是各种插件
但是 %ex
是没有参数,我们还得找一个带参数的实现
经过debug,参数在构造方法的参数 options
里面
因为我们还要输出行号这种带 location
信息的,我们看一下行号的实现
可以发现这个实现类实现了接口 LocationAware
那么我们的思路就清晰了,有了如下代码 PrettyClassPatternConverter
package com.github.hwhaocool.log;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.impl.LocationAware;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
/**
* @author yellowtail
* @since 2022/8/27 23:16
*/
@Plugin(
name = "LineLocationPatternConverter",
category = "Converter"
)
@ConverterKeys({"pc", "prettyClass"})
public class PrettyClassPatternConverter extends LogEventPatternConverter implements LocationAware {
protected PrettyClassPatternConverter(String name, String style) {
super(name, style);
}
private PrettyClassPatternConverter(final String[] options) {
this("PrettyClass", "prettyClass");
}
// 这个是被触发的得到实例的方法
public static PrettyClassPatternConverter newInstance(final String[] options) {
return new PrettyClassPatternConverter(options);
}
// 需要位置信息
@Override
public boolean requiresLocation() {
return true;
}
@Override
public void format(LogEvent event, StringBuilder toAppendTo) {
// 代码太长了就不贴了
}
}
代码在 github PrettyClassPatternConverter
输出
具体的实现就不多解释了,原则如下:
- 没有带长度参数,那就全输出
- 带了长度参数,但是没有那么长,就全输出,右侧补空格
- 带了长度参数,总长度超出了,且类名+方法+行号,加在一起也超出了,那么包名就按照每层只输出一位的最省模式输出(此时会出现
对不齐
) - 带了长度参数,总长度超出了,但是类名+方法+行号加在一起没有超出,那么包名就按照从右往左满足总长度要求的情况下尽量保留输出的原则(此时
能对齐
,有可能还会出现省略和空格同时出现的场景,因为总长度优先,包名输出了就超长,只能省略,省略了之后又不满足,就只能输出空格)
效果如下
%highlight{%pc}
无限制,此时不会对齐
%highlight{%pc{50}}
限制为50
此时可以看到效果
此时再试一下更短的
可以看到 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
尽力缩短了,还是会超出,没办法了,最后只能超出,导致对不齐
可以看到 o.s.boot.web.embedded.tomcat.TomcatWebServer
从右往左 在尽力输出
参考
log4j2-官网Pattern Layout
美团技术-# 日志导致线程Block的这些坑,你不得不防