记录一次 Logback日志打印行号不正确的问题

背景

由于业务上的需求需要在 Logback 打印的日志配置上增加行号信息,下面会介绍下如何增加行号打印的配置以及出现行号不正确问题的处理方案

使用

由于本文不是介绍 Logback 使用的文章,这里就不对功能性的介绍有过多的赘述

目前的日志打印是这个效果

目前 Loback 的配置如下

xml 复制代码
    <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 的常见子类,以及它们的具体作用:

  1. DateConverter:将日期时间格式化为特定的格式,并输出到日志中。
  2. LevelConverter:将日志级别转换为字符串,并输出到日志中。
  3. LineOfCallerConverter:获取调用者的堆栈信息中的行号,并输出到日志中。
  4. LoggerConverter:输出日志记录器的名称到日志中。
  5. MessageConverter:输出日志消息内容到日志中。
  6. MethodOfCallerConverter:获取调用者的堆栈信息中的方法名,并输出到日志中。
  7. RelativeTimeConverter:输出相对时间(相对于程序启动时间或者上一次事件的时间)到日志中。
  8. ThreadConverter:输出线程名称到日志中。
  9. ThrowableProxyConverter:输出异常堆栈跟踪信息到日志中。

如果我们要使用行号功能,只需要使用上图中DEFAULT_CONVERTER_MAP中放置的LineOfCallerConverter转换器对应的 Key 即可使用 即【%line】或者【%L】都代表使用行号转换器

使用方式如下:

xml 复制代码
    <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打印出来的行号都是固定的行数

java 复制代码
@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 兼容

那先看看之前的行号处理器是如何工作的

java 复制代码
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获取具体行号信息。

明白了工作原理我们实现一个自定义的行号转换器

java 复制代码
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" />

完整示例如下:

xml 复制代码
    <!-- 使用自定义行号转换器 -->  
    <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>
相关推荐
ok!ko3 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2401_857622663 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589363 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰4 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没5 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
编程、小哥哥5 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
IT学长编程6 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇6 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码6 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端