记录一次 Logback日志打印行号不正确的问题
背景
由于业务上的需求需要在 Logback 打印的日志配置上增加行号信息,下面会介绍下如何增加行号打印的配置以及出现行号不正确问题的处理方案
使用
由于本文不是介绍 Logback 使用的文章,这里就不对功能性的介绍有过多的赘述
目前的日志打印是这个效果
目前 Loback 的配置如下
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
根据 logback 的一些文档和网上的资料,查找到ch.qos.logback.classic.PatternLayout
这个类
这个类中内置了许多基础的 logback 变量,其中就包含了行号的内置变量和转换方法
这些转换器大多都继承自 ClassicConverter,还有些处理颜色的 converter
在 Logback 中,ClassicConverter 是用于定义自定义日志输出格式的基类。ClassicConverter 的子类通常用于扩展 Logback 的日志输出格式,以满足特定需求。
以下是一些 ClassicConverter 的常见子类,以及它们的具体作用:
- DateConverter:将日期时间格式化为特定的格式,并输出到日志中。
- LevelConverter:将日志级别转换为字符串,并输出到日志中。
- LineOfCallerConverter:获取调用者的堆栈信息中的行号,并输出到日志中。
- LoggerConverter:输出日志记录器的名称到日志中。
- MessageConverter:输出日志消息内容到日志中。
- MethodOfCallerConverter:获取调用者的堆栈信息中的方法名,并输出到日志中。
- RelativeTimeConverter:输出相对时间(相对于程序启动时间或者上一次事件的时间)到日志中。
- ThreadConverter:输出线程名称到日志中。
- ThrowableProxyConverter:输出异常堆栈跟踪信息到日志中。
如果我们要使用行号功能,只需要使用上图中DEFAULT_CONVERTER_MAP
中放置的LineOfCallerConverter
转换器对应的 Key 即可使用
即【%line】或者【%L】都代表使用行号转换器
使用方式如下:
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} [%line] %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
使用后日志打印多了行号信息
发现问题
本以为大功告成,在测试的过程中发现了一个新的问题
在使用 @Sl4J Lombok注解打印的行号基本都是正常的,但是在使用一个业务自定义的自定义日志类 BaseLogger打印出来的行号都是固定的行数
@Data
public class BaseLogger implements Logger {
private final Logger delegate;
public BaseLogger(Logger delegate) {
this.delegate = delegate;
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public void info(String msg) {
delegate.info(msg); <--------实际打印行数是这里,并非调用的者的实际行数
}
@Override
public void info(String format, Object arg) {
delegate.info(format, arg); <--------实际打印行数是这里,并非调用的者的实际行数
}
.......省略部分代码
}
解决问题
发现了问题,究其根本还是转换器处理的过程中没法对我们自定义的 logger 兼容
那先看看之前的行号处理器是如何工作的
public class LineOfCallerConverter extends ClassicConverter {
public LineOfCallerConverter() {
}
public String convert(ILoggingEvent le) {
StackTraceElement[] cda = le.getCallerData();
return cda != null && cda.length > 0 ? Integer.toString(cda[0].getLineNumber()) : "?";
}
}
代码就是获取当前代码执行堆栈信息,获取调用的上一级就是调用者StackTraceElement,在调用getLineNumber获取具体行号信息。
明白了工作原理我们实现一个自定义的行号转换器
public class CustomerLineOfCallerConverter extends ClassicConverter {
public String convert(ILoggingEvent le) {
StackTraceElement[] cda = le.getCallerData();
if (cda != null && cda.length > 0) {
StackTraceElement stackTraceElement = cda[0];
if (BaseLogger.class.getCanonicalName().equals(stackTraceElement.getClassName()) && cda.length > 1) {
return Integer.toString(cda[1].getLineNumber());
}
return Integer.toString(stackTraceElement.getLineNumber());
} else {
return CallerData.NA;
}
}
}
基于原有行号转换器增加了一个判断逻辑,如果当前堆栈的调用类是BaseLogger,那么再取他的上级的堆栈信息,之后调用getLineNumber就是真实调用者的行号
在 logback 的引入配置引用新的行号转换器替换掉默认的行号转换器
<conversionRule conversionWord="line" converterClass="com.ai94.core.logback.CustomerLineOfCallerConverter" />
完整示例如下:
<!-- 使用自定义行号转换器 -->
<conversionRule conversionWord="line" converterClass="com.ai94.core.logback.CustomerLineOfCallerConverter" />
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} [%line] %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
转载自:https://juejin.cn/post/7351319640553898023